rework television media (#26)
* rework television media * refactor poster saving * television and movie views are working again * remove dead code * use paper styling for all cards * add show poster, plot to seasons page * remove missing shows; cleanup interfaces * fix split show display (same show in different folders/sources) * add placeholder "add to schedule" button * no more duplicate television shows, even with the same show split across sources * stop releasing CLI for now * use season number as season placeholder * add television shows to collections * add television seasons to collections * add television episodes to collections * add movies to collections * remove movies, shows, seasons, episodes from collections * fix page width and menus * fix buffer size defaults * fix chronological episode ordering * allow deleting media collections * don't get stuck building a playout with an empty collection * schedule editing and playouts work again * minor cleanup * remove dead code * fix bugs with viewing movies as they are loading * add scanner tests; support nested movie folders * update collections docs * rearrange order of schedule items * add show and season to schedule * delete schedules that use legacy collections, reset all posters * move cleanup to new migration * load fallback metadata when nfo fails; don't require metadata in ui * update readme and screenshots
This commit is contained in:
@@ -39,24 +39,24 @@ jobs:
|
||||
# Define some variables for things we need
|
||||
tag=$(git describe --tags --abbrev=0)
|
||||
release_name="ErsatzTV-$tag-${{ matrix.target }}"
|
||||
release_name_cli="ErsatzTV.CommandLine-$tag-${{ matrix.target }}"
|
||||
#release_name_cli="ErsatzTV.CommandLine-$tag-${{ matrix.target }}"
|
||||
|
||||
# Build everything
|
||||
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net5.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" /property:InformationalVersion="${tag:1}-${{ matrix.target }}"
|
||||
dotnet publish ErsatzTV.CommandLine/ErsatzTV.CommandLine.csproj --framework net5.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name_cli" /property:InformationalVersion="${tag:1}-${{ matrix.target }}"
|
||||
#dotnet publish ErsatzTV.CommandLine/ErsatzTV.CommandLine.csproj --framework net5.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name_cli" /property:InformationalVersion="${tag:1}-${{ matrix.target }}"
|
||||
|
||||
# Pack files
|
||||
if [ "${{ matrix.target }}" == "win-x64" ]; then
|
||||
7z a -tzip "${release_name}.zip" "./${release_name}/*"
|
||||
7z a -tzip "${release_name_cli}.zip" "./${release_name_cli}/*"
|
||||
#7z a -tzip "${release_name_cli}.zip" "./${release_name_cli}/*"
|
||||
else
|
||||
tar czvf "${release_name}.tar.gz" "$release_name"
|
||||
tar czvf "${release_name_cli}.tar.gz" "$release_name_cli"
|
||||
#tar czvf "${release_name_cli}.tar.gz" "$release_name_cli"
|
||||
fi
|
||||
|
||||
# Delete output directory
|
||||
rm -r "$release_name"
|
||||
rm -r "$release_name_cli"
|
||||
#rm -r "$release_name_cli"
|
||||
|
||||
- name: Publish
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application
|
||||
{
|
||||
public interface IMediaCard
|
||||
{
|
||||
string Title { get; }
|
||||
string SortTitle { get; }
|
||||
string Subtitle { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
internal static class Mapper
|
||||
{
|
||||
internal static TelevisionShowCardViewModel ProjectToViewModel(TelevisionShow televisionShow) =>
|
||||
new(
|
||||
televisionShow.Id,
|
||||
televisionShow.Metadata?.Title,
|
||||
televisionShow.Metadata?.Year.ToString(),
|
||||
televisionShow.Metadata?.SortTitle,
|
||||
televisionShow.Poster);
|
||||
|
||||
internal static TelevisionSeasonCardViewModel ProjectToViewModel(TelevisionSeason televisionSeason) =>
|
||||
new(
|
||||
televisionSeason.TelevisionShow.Metadata?.Title,
|
||||
televisionSeason.Id,
|
||||
televisionSeason.Number,
|
||||
GetSeasonName(televisionSeason.Number),
|
||||
string.Empty,
|
||||
GetSeasonName(televisionSeason.Number),
|
||||
televisionSeason.Poster,
|
||||
televisionSeason.Number == 0 ? "S" : televisionSeason.Number.ToString());
|
||||
|
||||
internal static TelevisionEpisodeCardViewModel ProjectToViewModel(
|
||||
TelevisionEpisodeMediaItem televisionEpisode) =>
|
||||
new(
|
||||
televisionEpisode.Id,
|
||||
televisionEpisode.Metadata?.Aired ?? DateTime.MinValue,
|
||||
televisionEpisode.Season.TelevisionShow.Metadata.Title,
|
||||
televisionEpisode.Metadata?.Title,
|
||||
$"Episode {televisionEpisode.Metadata?.Episode}",
|
||||
televisionEpisode.Metadata?.Episode.ToString(),
|
||||
televisionEpisode.Poster,
|
||||
televisionEpisode.Metadata?.Episode.ToString());
|
||||
|
||||
internal static MovieCardViewModel ProjectToViewModel(MovieMediaItem movie) =>
|
||||
new(
|
||||
movie.Id,
|
||||
movie.Metadata?.Title,
|
||||
movie.Metadata?.Year?.ToString(),
|
||||
movie.Metadata?.SortTitle,
|
||||
movie.Poster);
|
||||
|
||||
internal static SimpleMediaCollectionCardResultsViewModel
|
||||
ProjectToViewModel(SimpleMediaCollection collection) =>
|
||||
new(
|
||||
collection.Name,
|
||||
collection.Movies.Map(ProjectToViewModel).ToList(),
|
||||
collection.TelevisionShows.Map(ProjectToViewModel).ToList(),
|
||||
collection.TelevisionSeasons.Map(ProjectToViewModel).ToList(),
|
||||
collection.TelevisionEpisodes.Map(ProjectToViewModel).ToList());
|
||||
|
||||
private static string GetSeasonName(int number) =>
|
||||
number == 0 ? "Specials" : $"Season {number}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record MediaCardViewModel(string Title, string Subtitle, string SortTitle, string Poster);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record MovieCardResultsViewModel(int Count, List<MovieCardViewModel> Cards);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record MovieCardViewModel
|
||||
(int MovieId, string Title, string Subtitle, string SortTitle, string Poster) : MediaCardViewModel(
|
||||
Title,
|
||||
Subtitle,
|
||||
SortTitle,
|
||||
Poster)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetMovieCards(int PageNumber, int PageSize) : IRequest<MovieCardResultsViewModel>;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class
|
||||
GetMovieCardsHandler : IRequestHandler<GetMovieCards, MovieCardResultsViewModel>
|
||||
{
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
|
||||
public GetMovieCardsHandler(IMovieRepository movieRepository) => _movieRepository = movieRepository;
|
||||
|
||||
public async Task<MovieCardResultsViewModel> Handle(
|
||||
GetMovieCards request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
int count = await _movieRepository.GetMovieCount();
|
||||
|
||||
List<MovieCardViewModel> results = await _movieRepository
|
||||
.GetPagedMovies(request.PageNumber, request.PageSize)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
|
||||
return new MovieCardResultsViewModel(count, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetSimpleMediaCollectionCards
|
||||
(int Id) : IRequest<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class GetSimpleMediaCollectionCardsHandler : IRequestHandler<GetSimpleMediaCollectionCards,
|
||||
Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
|
||||
public GetSimpleMediaCollectionCardsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
|
||||
public async Task<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>> Handle(
|
||||
GetSimpleMediaCollectionCards request,
|
||||
CancellationToken cancellationToken) =>
|
||||
(await _mediaCollectionRepository.GetSimpleMediaCollectionWithItemsUntracked(request.Id))
|
||||
.ToEither(BaseError.New("Unable to load collection"))
|
||||
.Map(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetTelevisionEpisodeCards
|
||||
(int TelevisionSeasonId, int PageNumber, int PageSize) : IRequest<TelevisionEpisodeCardResultsViewModel>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class
|
||||
GetTelevisionEpisodeCardsHandler : IRequestHandler<GetTelevisionEpisodeCards,
|
||||
TelevisionEpisodeCardResultsViewModel>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionEpisodeCardsHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public async Task<TelevisionEpisodeCardResultsViewModel> Handle(
|
||||
GetTelevisionEpisodeCards request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
int count = await _televisionRepository.GetEpisodeCount(request.TelevisionSeasonId);
|
||||
|
||||
List<TelevisionEpisodeCardViewModel> results = await _televisionRepository
|
||||
.GetPagedEpisodes(request.TelevisionSeasonId, request.PageNumber, request.PageSize)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
|
||||
return new TelevisionEpisodeCardResultsViewModel(count, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetTelevisionSeasonCards
|
||||
(int TelevisionShowId, int PageNumber, int PageSize) : IRequest<TelevisionSeasonCardResultsViewModel>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class
|
||||
GetTelevisionSeasonCardsHandler : IRequestHandler<GetTelevisionSeasonCards, TelevisionSeasonCardResultsViewModel
|
||||
>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionSeasonCardsHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public async Task<TelevisionSeasonCardResultsViewModel> Handle(
|
||||
GetTelevisionSeasonCards request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
int count = await _televisionRepository.GetSeasonCount(request.TelevisionShowId);
|
||||
|
||||
List<TelevisionSeasonCardViewModel> results = await _televisionRepository
|
||||
.GetPagedSeasons(request.TelevisionShowId, request.PageNumber, request.PageSize)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
|
||||
return new TelevisionSeasonCardResultsViewModel(count, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetTelevisionShowCards(int PageNumber, int PageSize) : IRequest<TelevisionShowCardResultsViewModel>;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class
|
||||
GetTelevisionShowCardsHandler : IRequestHandler<GetTelevisionShowCards, TelevisionShowCardResultsViewModel>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionShowCardsHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public async Task<TelevisionShowCardResultsViewModel> Handle(
|
||||
GetTelevisionShowCards request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
int count = await _televisionRepository.GetShowCount();
|
||||
|
||||
List<TelevisionShowCardViewModel> results = await _televisionRepository
|
||||
.GetPagedShows(request.PageNumber, request.PageSize)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
|
||||
return new TelevisionShowCardResultsViewModel(count, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record SimpleMediaCollectionCardResultsViewModel(
|
||||
string Name,
|
||||
List<MovieCardViewModel> MovieCards,
|
||||
List<TelevisionShowCardViewModel> ShowCards,
|
||||
List<TelevisionSeasonCardViewModel> SeasonCards,
|
||||
List<TelevisionEpisodeCardViewModel> EpisodeCards);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionEpisodeCardResultsViewModel(int Count, List<TelevisionEpisodeCardViewModel> Cards);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionEpisodeCardViewModel
|
||||
(
|
||||
int EpisodeId,
|
||||
DateTime Aired,
|
||||
string ShowTitle,
|
||||
string Title,
|
||||
string Subtitle,
|
||||
string SortTitle,
|
||||
string Poster,
|
||||
string Placeholder) : MediaCardViewModel(
|
||||
Title,
|
||||
Subtitle,
|
||||
SortTitle,
|
||||
Poster)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionSeasonCardResultsViewModel(int Count, List<TelevisionSeasonCardViewModel> Cards);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionSeasonCardViewModel
|
||||
(
|
||||
string ShowTitle,
|
||||
int TelevisionSeasonId,
|
||||
int TelevisionSeasonNumber,
|
||||
string Title,
|
||||
string Subtitle,
|
||||
string SortTitle,
|
||||
string Poster,
|
||||
string Placeholder) : MediaCardViewModel(
|
||||
Title,
|
||||
Subtitle,
|
||||
SortTitle,
|
||||
Poster)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionShowCardResultsViewModel(int Count, List<TelevisionShowCardViewModel> Cards);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ErsatzTV.Application.MediaCards
|
||||
{
|
||||
public record TelevisionShowCardViewModel
|
||||
(int TelevisionShowId, string Title, string Subtitle, string SortTitle, string Poster) : MediaCardViewModel(
|
||||
Title,
|
||||
Subtitle,
|
||||
SortTitle,
|
||||
Poster)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record AddItemsToSimpleMediaCollection
|
||||
(int MediaCollectionId, List<int> ItemIds) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
AddItemsToSimpleMediaCollectionHandler : MediatR.IRequestHandler<AddItemsToSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public AddItemsToSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
IMediaItemRepository mediaItemRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
AddItemsToSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(ApplyAddItemsRequest)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> ApplyAddItemsRequest(RequestParameters parameters)
|
||||
{
|
||||
foreach (MediaItem item in parameters.ItemsToAdd.Where(
|
||||
item => parameters.Collection.Items.All(i => i.Id != item.Id)))
|
||||
{
|
||||
parameters.Collection.Items.Add(item);
|
||||
}
|
||||
|
||||
await _mediaCollectionRepository.Update(parameters.Collection);
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>>
|
||||
Validate(AddItemsToSimpleMediaCollection request) =>
|
||||
(await SimpleMediaCollectionMustExist(request), await ValidateItems(request))
|
||||
.Apply(
|
||||
(simpleMediaCollectionToUpdate, itemsToAdd) =>
|
||||
new RequestParameters(simpleMediaCollectionToUpdate, itemsToAdd));
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
AddItemsToSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollection(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
|
||||
private Task<Validation<BaseError, List<MediaItem>>> ValidateItems(
|
||||
AddItemsToSimpleMediaCollection request) =>
|
||||
LoadAllMediaItems(request)
|
||||
.Map(v => v.ToValidation<BaseError>("MediaItem does not exist"));
|
||||
|
||||
private async Task<Option<List<MediaItem>>> LoadAllMediaItems(AddItemsToSimpleMediaCollection request)
|
||||
{
|
||||
var items = (await request.ItemIds.Map(async id => await _mediaItemRepository.Get(id)).Sequence())
|
||||
.ToList();
|
||||
if (items.Any(i => i.IsNone))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
return items.Somes().ToList();
|
||||
}
|
||||
|
||||
private record RequestParameters(SimpleMediaCollection Collection, List<MediaItem> ItemsToAdd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record AddMovieToSimpleMediaCollection
|
||||
(int MediaCollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
AddMovieToSimpleMediaCollectionHandler : MediatR.IRequestHandler<AddMovieToSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
|
||||
public AddMovieToSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
IMovieRepository movieRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_movieRepository = movieRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
AddMovieToSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(ApplyAddMoviesRequest)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> ApplyAddMoviesRequest(RequestParameters parameters)
|
||||
{
|
||||
parameters.Collection.Movies.Add(parameters.MovieToAdd);
|
||||
await _mediaCollectionRepository.Update(parameters.Collection);
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>>
|
||||
Validate(AddMovieToSimpleMediaCollection request) =>
|
||||
(await SimpleMediaCollectionMustExist(request), await ValidateMovies(request))
|
||||
.Apply(
|
||||
(simpleMediaCollectionToUpdate, movieToAdd) =>
|
||||
new RequestParameters(simpleMediaCollectionToUpdate, movieToAdd));
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
AddMovieToSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
|
||||
private Task<Validation<BaseError, MovieMediaItem>> ValidateMovies(
|
||||
AddMovieToSimpleMediaCollection request) =>
|
||||
LoadMovie(request)
|
||||
.Map(v => v.ToValidation<BaseError>("MovieMediaItem does not exist"));
|
||||
|
||||
private Task<Option<MovieMediaItem>> LoadMovie(AddMovieToSimpleMediaCollection request) =>
|
||||
_movieRepository.GetMovie(request.MovieId);
|
||||
|
||||
private record RequestParameters(SimpleMediaCollection Collection, MovieMediaItem MovieToAdd);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record AddTelevisionEpisodeToSimpleMediaCollection
|
||||
(int MediaCollectionId, int TelevisionEpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
AddTelevisionEpisodeToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
|
||||
AddTelevisionEpisodeToSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public AddTelevisionEpisodeToSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
ITelevisionRepository televisionRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_televisionRepository = televisionRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
AddTelevisionEpisodeToSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(ApplyAddTelevisionEpisodeRequest)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> ApplyAddTelevisionEpisodeRequest(RequestParameters parameters)
|
||||
{
|
||||
if (parameters.Collection.TelevisionEpisodes.All(s => s.Id != parameters.EpisodeToAdd.Id))
|
||||
{
|
||||
parameters.Collection.TelevisionEpisodes.Add(parameters.EpisodeToAdd);
|
||||
await _mediaCollectionRepository.Update(parameters.Collection);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>>
|
||||
Validate(AddTelevisionEpisodeToSimpleMediaCollection request) =>
|
||||
(await SimpleMediaCollectionMustExist(request), await ValidateEpisode(request))
|
||||
.Apply(
|
||||
(simpleMediaCollectionToUpdate, episode) =>
|
||||
new RequestParameters(simpleMediaCollectionToUpdate, episode));
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
AddTelevisionEpisodeToSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
|
||||
private Task<Validation<BaseError, TelevisionEpisodeMediaItem>> ValidateEpisode(
|
||||
AddTelevisionEpisodeToSimpleMediaCollection request) =>
|
||||
LoadTelevisionEpisode(request)
|
||||
.Map(v => v.ToValidation<BaseError>("TelevisionEpisode does not exist"));
|
||||
|
||||
private Task<Option<TelevisionEpisodeMediaItem>> LoadTelevisionEpisode(
|
||||
AddTelevisionEpisodeToSimpleMediaCollection request) =>
|
||||
_televisionRepository.GetEpisode(request.TelevisionEpisodeId);
|
||||
|
||||
private record RequestParameters(SimpleMediaCollection Collection, TelevisionEpisodeMediaItem EpisodeToAdd);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record AddTelevisionSeasonToSimpleMediaCollection
|
||||
(int MediaCollectionId, int TelevisionSeasonId) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
AddTelevisionSeasonToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
|
||||
AddTelevisionSeasonToSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public AddTelevisionSeasonToSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
ITelevisionRepository televisionRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_televisionRepository = televisionRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
AddTelevisionSeasonToSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(ApplyAddTelevisionSeasonRequest)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> ApplyAddTelevisionSeasonRequest(RequestParameters parameters)
|
||||
{
|
||||
if (parameters.Collection.TelevisionSeasons.All(s => s.Id != parameters.SeasonToAdd.Id))
|
||||
{
|
||||
parameters.Collection.TelevisionSeasons.Add(parameters.SeasonToAdd);
|
||||
await _mediaCollectionRepository.Update(parameters.Collection);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>>
|
||||
Validate(AddTelevisionSeasonToSimpleMediaCollection request) =>
|
||||
(await SimpleMediaCollectionMustExist(request), await ValidateSeason(request))
|
||||
.Apply(
|
||||
(simpleMediaCollectionToUpdate, season) =>
|
||||
new RequestParameters(simpleMediaCollectionToUpdate, season));
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
AddTelevisionSeasonToSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
|
||||
private Task<Validation<BaseError, TelevisionSeason>> ValidateSeason(
|
||||
AddTelevisionSeasonToSimpleMediaCollection request) =>
|
||||
LoadTelevisionSeason(request)
|
||||
.Map(v => v.ToValidation<BaseError>("TelevisionSeason does not exist"));
|
||||
|
||||
private Task<Option<TelevisionSeason>> LoadTelevisionSeason(
|
||||
AddTelevisionSeasonToSimpleMediaCollection request) =>
|
||||
_televisionRepository.GetSeason(request.TelevisionSeasonId);
|
||||
|
||||
private record RequestParameters(SimpleMediaCollection Collection, TelevisionSeason SeasonToAdd);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record AddTelevisionShowToSimpleMediaCollection
|
||||
(int MediaCollectionId, int TelevisionShowId) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
AddTelevisionShowToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
|
||||
AddTelevisionShowToSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public AddTelevisionShowToSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
ITelevisionRepository televisionRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_televisionRepository = televisionRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
AddTelevisionShowToSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(ApplyAddTelevisionShowRequest)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> ApplyAddTelevisionShowRequest(RequestParameters parameters)
|
||||
{
|
||||
if (parameters.Collection.TelevisionShows.All(s => s.Id != parameters.ShowToAdd.Id))
|
||||
{
|
||||
parameters.Collection.TelevisionShows.Add(parameters.ShowToAdd);
|
||||
await _mediaCollectionRepository.Update(parameters.Collection);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>>
|
||||
Validate(AddTelevisionShowToSimpleMediaCollection request) =>
|
||||
(await SimpleMediaCollectionMustExist(request), await ValidateShow(request))
|
||||
.Apply(
|
||||
(simpleMediaCollectionToUpdate, show) =>
|
||||
new RequestParameters(simpleMediaCollectionToUpdate, show));
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
AddTelevisionShowToSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
|
||||
private Task<Validation<BaseError, TelevisionShow>> ValidateShow(
|
||||
AddTelevisionShowToSimpleMediaCollection request) =>
|
||||
LoadTelevisionShow(request)
|
||||
.Map(v => v.ToValidation<BaseError>("TelevisionShow does not exist"));
|
||||
|
||||
private Task<Option<TelevisionShow>> LoadTelevisionShow(AddTelevisionShowToSimpleMediaCollection request) =>
|
||||
_televisionRepository.GetShow(request.TelevisionShowId);
|
||||
|
||||
private record RequestParameters(SimpleMediaCollection Collection, TelevisionShow ShowToAdd);
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -29,7 +29,15 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
_mediaCollectionRepository.Add(c).Map(ProjectToViewModel);
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(CreateSimpleMediaCollection request) =>
|
||||
ValidateName(request).MapT(name => new SimpleMediaCollection { Name = name });
|
||||
ValidateName(request).MapT(
|
||||
name => new SimpleMediaCollection
|
||||
{
|
||||
Name = name,
|
||||
Movies = new List<MovieMediaItem>(),
|
||||
TelevisionShows = new List<TelevisionShow>(),
|
||||
TelevisionEpisodes = new List<TelevisionEpisodeMediaItem>(),
|
||||
TelevisionSeasons = new List<TelevisionSeason>()
|
||||
});
|
||||
|
||||
private async Task<Validation<BaseError, string>> ValidateName(CreateSimpleMediaCollection createCollection)
|
||||
{
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record RemoveItemsFromSimpleMediaCollection
|
||||
(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>>
|
||||
{
|
||||
public List<int> MovieIds { get; set; } = new();
|
||||
public List<int> TelevisionShowIds { get; set; } = new();
|
||||
public List<int> TelevisionSeasonIds { get; set; } = new();
|
||||
public List<int> TelevisionEpisodeIds { get; set; } = new();
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class
|
||||
RemoveItemsFromSimpleMediaCollectionHandler : MediatR.IRequestHandler<
|
||||
RemoveItemsFromSimpleMediaCollection,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
|
||||
public RemoveItemsFromSimpleMediaCollectionHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository) =>
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
RemoveItemsFromSimpleMediaCollection request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private Task<Unit> ApplyAddTelevisionEpisodeRequest(
|
||||
RemoveItemsFromSimpleMediaCollection request,
|
||||
SimpleMediaCollection collection)
|
||||
{
|
||||
var moviesToRemove = collection.Movies
|
||||
.Filter(m => request.MovieIds.Contains(m.Id))
|
||||
.ToList();
|
||||
|
||||
moviesToRemove.ForEach(m => collection.Movies.Remove(m));
|
||||
|
||||
var showsToRemove = collection.TelevisionShows
|
||||
.Filter(s => request.TelevisionShowIds.Contains(s.Id))
|
||||
.ToList();
|
||||
|
||||
showsToRemove.ForEach(s => collection.TelevisionShows.Remove(s));
|
||||
|
||||
var seasonsToRemove = collection.TelevisionSeasons
|
||||
.Filter(s => request.TelevisionSeasonIds.Contains(s.Id))
|
||||
.ToList();
|
||||
|
||||
seasonsToRemove.ForEach(s => collection.TelevisionSeasons.Remove(s));
|
||||
|
||||
var episodesToRemove = collection.TelevisionEpisodes
|
||||
.Filter(e => request.TelevisionEpisodeIds.Contains(e.Id))
|
||||
.ToList();
|
||||
|
||||
episodesToRemove.ForEach(e => collection.TelevisionEpisodes.Remove(e));
|
||||
|
||||
if (moviesToRemove.Any() || showsToRemove.Any() || seasonsToRemove.Any() || episodesToRemove.Any())
|
||||
{
|
||||
return _mediaCollectionRepository.Update(collection).ToUnit();
|
||||
}
|
||||
|
||||
return Task.FromResult(Unit.Default);
|
||||
}
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(
|
||||
RemoveItemsFromSimpleMediaCollection request) =>
|
||||
SimpleMediaCollectionMustExist(request);
|
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
|
||||
RemoveItemsFromSimpleMediaCollection updateSimpleMediaCollection) =>
|
||||
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
|
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public record ReplaceSimpleMediaCollectionItems
|
||||
(int MediaCollectionId, List<int> MediaItemIds) : IRequest<Either<BaseError, List<MediaItemViewModel>>>;
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using LanguageExt.UnsafeValueAccess;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
{
|
||||
public class ReplaceSimpleMediaCollectionItemsHandler : IRequestHandler<ReplaceSimpleMediaCollectionItems,
|
||||
Either<BaseError, List<MediaItemViewModel>>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public ReplaceSimpleMediaCollectionItemsHandler(
|
||||
IMediaCollectionRepository mediaCollectionRepository,
|
||||
IMediaItemRepository mediaItemRepository)
|
||||
{
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, List<MediaItemViewModel>>> Handle(
|
||||
ReplaceSimpleMediaCollectionItems request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(mediaItems => PersistItems(request, mediaItems))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<List<MediaItemViewModel>> PersistItems(
|
||||
ReplaceSimpleMediaCollectionItems request,
|
||||
List<MediaItem> mediaItems)
|
||||
{
|
||||
await _mediaCollectionRepository.ReplaceItems(request.MediaCollectionId, mediaItems);
|
||||
return mediaItems.Map(MediaItems.Mapper.ProjectToViewModel).ToList();
|
||||
}
|
||||
|
||||
private Task<Validation<BaseError, List<MediaItem>>> Validate(ReplaceSimpleMediaCollectionItems request) =>
|
||||
MediaCollectionMustExist(request).BindT(_ => MediaItemsMustExist(request));
|
||||
|
||||
private async Task<Validation<BaseError, SimpleMediaCollection>> MediaCollectionMustExist(
|
||||
ReplaceSimpleMediaCollectionItems request) =>
|
||||
(await _mediaCollectionRepository.GetSimpleMediaCollection(request.MediaCollectionId))
|
||||
.ToValidation<BaseError>("[MediaCollectionId] does not exist.");
|
||||
|
||||
private async Task<Validation<BaseError, List<MediaItem>>> MediaItemsMustExist(
|
||||
ReplaceSimpleMediaCollectionItems replaceItems)
|
||||
{
|
||||
var allMediaItems = (await replaceItems.MediaItemIds.Map(i => _mediaItemRepository.Get(i)).Sequence())
|
||||
.ToList();
|
||||
if (allMediaItems.Any(x => x.IsNone))
|
||||
{
|
||||
return BaseError.New("[MediaItemId] does not exist");
|
||||
}
|
||||
|
||||
return allMediaItems.Sequence().ValueUnsafe().ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using ErsatzTV.Core.AggregateModels;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections
|
||||
{
|
||||
@@ -7,13 +6,5 @@ namespace ErsatzTV.Application.MediaCollections
|
||||
{
|
||||
internal static MediaCollectionViewModel ProjectToViewModel(MediaCollection mediaCollection) =>
|
||||
new(mediaCollection.Id, mediaCollection.Name);
|
||||
|
||||
internal static MediaCollectionSummaryViewModel ProjectToViewModel(
|
||||
MediaCollectionSummary mediaCollectionSummary) =>
|
||||
new(
|
||||
mediaCollectionSummary.Id,
|
||||
mediaCollectionSummary.Name,
|
||||
mediaCollectionSummary.ItemCount,
|
||||
mediaCollectionSummary.IsSimple);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
namespace ErsatzTV.Application.MediaCollections
|
||||
using ErsatzTV.Application.MediaCards;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections
|
||||
{
|
||||
public record MediaCollectionViewModel(int Id, string Name);
|
||||
public record MediaCollectionViewModel(int Id, string Name) : MediaCardViewModel(
|
||||
Name,
|
||||
string.Empty,
|
||||
Name,
|
||||
string.Empty);
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries
|
||||
{
|
||||
public record GetMediaCollectionSummaries(string SearchString) : IRequest<List<MediaCollectionSummaryViewModel>>;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.MediaCollections.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries
|
||||
{
|
||||
public class
|
||||
GetMediaCollectionSummariesHandler : IRequestHandler<GetMediaCollectionSummaries,
|
||||
List<MediaCollectionSummaryViewModel>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
|
||||
public GetMediaCollectionSummariesHandler(IMediaCollectionRepository mediaCollectionRepository) =>
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
|
||||
public Task<List<MediaCollectionSummaryViewModel>> Handle(
|
||||
GetMediaCollectionSummaries request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_mediaCollectionRepository.GetSummaries(request.SearchString)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries
|
||||
{
|
||||
public record GetSimpleMediaCollectionWithItemsById
|
||||
(int Id) : IRequest<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>>;
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Application.MediaItems;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static LanguageExt.Prelude;
|
||||
using static ErsatzTV.Application.MediaCollections.Mapper;
|
||||
using static ErsatzTV.Application.MediaItems.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries
|
||||
{
|
||||
public class GetSimpleMediaCollectionWithItemsByIdHandler : IRequestHandler<GetSimpleMediaCollectionWithItemsById,
|
||||
Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
|
||||
public GetSimpleMediaCollectionWithItemsByIdHandler(IMediaCollectionRepository mediaCollectionRepository) =>
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
|
||||
public async Task<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>> Handle(
|
||||
GetSimpleMediaCollectionWithItemsById request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Option<SimpleMediaCollection> maybeCollection =
|
||||
await _mediaCollectionRepository.GetSimpleMediaCollectionWithItems(request.Id);
|
||||
|
||||
return maybeCollection.Match<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>>(
|
||||
c => Tuple(ProjectToViewModel(c), c.Items.Map(ProjectToSearchViewModel).ToList()),
|
||||
None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems
|
||||
{
|
||||
public record AggregateMediaItemResults(int Count, List<AggregateMediaItemViewModel> DataPage);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems
|
||||
{
|
||||
public record AggregateMediaItemViewModel(
|
||||
int MediaItemId,
|
||||
string Title,
|
||||
string Subtitle,
|
||||
string SortTitle,
|
||||
string Poster);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record CreateMediaItem(int MediaSourceId, string Path) : IRequest<Either<BaseError, MediaItemViewModel>>;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static LanguageExt.Prelude;
|
||||
using static ErsatzTV.Application.MediaItems.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class CreateMediaItemHandler : IRequestHandler<CreateMediaItem, Either<BaseError, MediaItemViewModel>>
|
||||
{
|
||||
private readonly IConfigElementRepository _configElementRepository;
|
||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||
private readonly ILocalPosterProvider _localPosterProvider;
|
||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly ISmartCollectionBuilder _smartCollectionBuilder;
|
||||
|
||||
public CreateMediaItemHandler(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ISmartCollectionBuilder smartCollectionBuilder,
|
||||
ILocalMetadataProvider localMetadataProvider,
|
||||
ILocalStatisticsProvider localStatisticsProvider,
|
||||
ILocalPosterProvider localPosterProvider)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_configElementRepository = configElementRepository;
|
||||
_smartCollectionBuilder = smartCollectionBuilder;
|
||||
_localMetadataProvider = localMetadataProvider;
|
||||
_localStatisticsProvider = localStatisticsProvider;
|
||||
_localPosterProvider = localPosterProvider;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, MediaItemViewModel>> Handle(
|
||||
CreateMediaItem request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(PersistMediaItem)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<MediaItemViewModel> PersistMediaItem(RequestParameters parameters)
|
||||
{
|
||||
await _mediaItemRepository.Add(parameters.MediaItem);
|
||||
|
||||
await _localStatisticsProvider.RefreshStatistics(parameters.FFprobePath, parameters.MediaItem);
|
||||
// TODO: reimplement this
|
||||
// await _localMetadataProvider.RefreshMetadata(parameters.MediaItem);
|
||||
// await _localPosterProvider.RefreshPoster(parameters.MediaItem);
|
||||
// await _smartCollectionBuilder.RefreshSmartCollections(parameters.MediaItem);
|
||||
|
||||
return ProjectToViewModel(parameters.MediaItem);
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>> Validate(CreateMediaItem request) =>
|
||||
(await ValidateMediaSource(request), PathMustExist(request), await ValidateFFprobePath())
|
||||
.Apply(
|
||||
(mediaSourceId, path, ffprobePath) => new RequestParameters(
|
||||
ffprobePath,
|
||||
new MediaItem
|
||||
{
|
||||
MediaSourceId = mediaSourceId,
|
||||
Path = path
|
||||
}));
|
||||
|
||||
private async Task<Validation<BaseError, int>> ValidateMediaSource(CreateMediaItem createMediaItem) =>
|
||||
(await MediaSourceMustExist(createMediaItem)).Bind(MediaSourceMustBeLocal);
|
||||
|
||||
private async Task<Validation<BaseError, MediaSource>> MediaSourceMustExist(CreateMediaItem createMediaItem) =>
|
||||
(await _mediaSourceRepository.Get(createMediaItem.MediaSourceId))
|
||||
.ToValidation<BaseError>($"[MediaSource] {createMediaItem.MediaSourceId} does not exist.");
|
||||
|
||||
private Validation<BaseError, int> MediaSourceMustBeLocal(MediaSource mediaSource) =>
|
||||
Some(mediaSource)
|
||||
.Filter(ms => ms is LocalMediaSource)
|
||||
.ToValidation<BaseError>($"[MediaSource] {mediaSource.Id} must be a local media source")
|
||||
.Map(ms => ms.Id);
|
||||
|
||||
private Validation<BaseError, string> PathMustExist(CreateMediaItem createMediaItem) =>
|
||||
Some(createMediaItem.Path)
|
||||
.Filter(File.Exists)
|
||||
.ToValidation<BaseError>("[Path] does not exist on the file system");
|
||||
|
||||
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
|
||||
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
|
||||
.FilterT(File.Exists)
|
||||
.Map(
|
||||
ffprobePath =>
|
||||
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system"));
|
||||
|
||||
private record RequestParameters(string FFprobePath, MediaItem MediaItem);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record DeleteMediaItem(int MediaItemId) : IRequest<Either<BaseError, Task>>;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class DeleteMediaItemHandler : IRequestHandler<DeleteMediaItem, Either<BaseError, Task>>
|
||||
{
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public DeleteMediaItemHandler(IMediaItemRepository mediaItemRepository) =>
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
|
||||
public async Task<Either<BaseError, Task>> Handle(
|
||||
DeleteMediaItem request,
|
||||
CancellationToken cancellationToken) =>
|
||||
(await MediaItemMustExist(request))
|
||||
.Map(DoDeletion)
|
||||
.ToEither<Task>();
|
||||
|
||||
private Task DoDeletion(int mediaItemId) => _mediaItemRepository.Delete(mediaItemId);
|
||||
|
||||
private async Task<Validation<BaseError, int>> MediaItemMustExist(DeleteMediaItem deleteMediaItem) =>
|
||||
(await _mediaItemRepository.Get(deleteMediaItem.MediaItemId))
|
||||
.ToValidation<BaseError>($"MediaItem {deleteMediaItem.MediaItemId} does not exist.")
|
||||
.Map(c => c.Id);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record RefreshMediaItem(int MediaItemId) : MediatR.IRequest<Either<BaseError, Unit>>,
|
||||
IBackgroundServiceRequest;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record RefreshMediaItemCollections : RefreshMediaItem
|
||||
{
|
||||
public RefreshMediaItemCollections(int mediaItemId) : base(mediaItemId)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class
|
||||
RefreshMediaItemCollectionsHandler : MediatR.IRequestHandler<RefreshMediaItemCollections,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
private readonly ISmartCollectionBuilder _smartCollectionBuilder;
|
||||
|
||||
public RefreshMediaItemCollectionsHandler(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
ISmartCollectionBuilder smartCollectionBuilder)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_smartCollectionBuilder = smartCollectionBuilder;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
RefreshMediaItemCollections request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(RefreshCollections)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> Validate(RefreshMediaItemCollections request) =>
|
||||
MediaItemMustExist(request);
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> MediaItemMustExist(
|
||||
RefreshMediaItemCollections refreshMediaItemCollections) =>
|
||||
_mediaItemRepository.Get(refreshMediaItemCollections.MediaItemId)
|
||||
.Map(
|
||||
maybeItem => maybeItem.ToValidation<BaseError>(
|
||||
$"[MediaItem] {refreshMediaItemCollections.MediaItemId} does not exist."));
|
||||
|
||||
private Task<Unit> RefreshCollections(MediaItem mediaItem) =>
|
||||
_smartCollectionBuilder.RefreshSmartCollections(mediaItem).ToUnit();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record RefreshMediaItemMetadata : RefreshMediaItem
|
||||
{
|
||||
public RefreshMediaItemMetadata(int mediaItemId) : base(mediaItemId)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class
|
||||
RefreshMediaItemMetadataHandler : MediatR.IRequestHandler<RefreshMediaItemMetadata, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public RefreshMediaItemMetadataHandler(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
ILocalMetadataProvider localMetadataProvider)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_localMetadataProvider = localMetadataProvider;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
RefreshMediaItemMetadata request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(RefreshMetadata)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> Validate(RefreshMediaItemMetadata request) =>
|
||||
MediaItemMustExist(request).BindT(PathMustExist);
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> MediaItemMustExist(
|
||||
RefreshMediaItemMetadata refreshMediaItemMetadata) =>
|
||||
_mediaItemRepository.Get(refreshMediaItemMetadata.MediaItemId)
|
||||
.Map(
|
||||
maybeItem => maybeItem.ToValidation<BaseError>(
|
||||
$"[MediaItem] {refreshMediaItemMetadata.MediaItemId} does not exist."));
|
||||
|
||||
private Validation<BaseError, MediaItem> PathMustExist(MediaItem mediaItem) =>
|
||||
Some(mediaItem)
|
||||
.Filter(item => File.Exists(item.Path))
|
||||
.ToValidation<BaseError>($"[Path] '{mediaItem.Path}' does not exist on the file system");
|
||||
|
||||
private Task<Unit> RefreshMetadata(MediaItem mediaItem) => Task.CompletedTask.ToUnit();
|
||||
// TODO: reimplement this
|
||||
// _localMetadataProvider.RefreshMetadata(mediaItem).ToUnit();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record RefreshMediaItemPoster : RefreshMediaItem
|
||||
{
|
||||
public RefreshMediaItemPoster(int mediaItemId) : base(mediaItemId)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class
|
||||
RefreshMediaItemPosterHandler : MediatR.IRequestHandler<RefreshMediaItemPoster,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ILocalPosterProvider _localPosterProvider;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public RefreshMediaItemPosterHandler(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
ILocalPosterProvider localPosterProvider)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_localPosterProvider = localPosterProvider;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
RefreshMediaItemPoster request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(RefreshPoster)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> Validate(RefreshMediaItemPoster request) =>
|
||||
MediaItemMustExist(request);
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> MediaItemMustExist(RefreshMediaItemPoster request) =>
|
||||
_mediaItemRepository.Get(request.MediaItemId)
|
||||
.Map(
|
||||
maybeItem => maybeItem.ToValidation<BaseError>(
|
||||
$"[MediaItem] {request.MediaItemId} does not exist."));
|
||||
|
||||
private Task<Unit> RefreshPoster(MediaItem mediaItem) =>
|
||||
_localPosterProvider.RefreshPoster(mediaItem).ToUnit();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public record RefreshMediaItemStatistics : RefreshMediaItem
|
||||
{
|
||||
public RefreshMediaItemStatistics(int mediaItemId) : base(mediaItemId)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Commands
|
||||
{
|
||||
public class
|
||||
RefreshMediaItemStatisticsHandler : MediatR.IRequestHandler<RefreshMediaItemStatistics, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IConfigElementRepository _configElementRepository;
|
||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public RefreshMediaItemStatisticsHandler(
|
||||
IMediaItemRepository mediaItemRepository,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILocalStatisticsProvider localStatisticsProvider)
|
||||
{
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
_configElementRepository = configElementRepository;
|
||||
_localStatisticsProvider = localStatisticsProvider;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
RefreshMediaItemStatistics request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(RefreshStatistics)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Validation<BaseError, RefreshParameters>> Validate(RefreshMediaItemStatistics request) =>
|
||||
(await MediaItemMustExist(request).BindT(PathMustExist), await ValidateFFprobePath())
|
||||
.Apply((mediaItem, ffprobePath) => new RefreshParameters(mediaItem, ffprobePath));
|
||||
|
||||
private Task<Validation<BaseError, MediaItem>> MediaItemMustExist(
|
||||
RefreshMediaItemStatistics refreshMediaItemStatistics) =>
|
||||
_mediaItemRepository.Get(refreshMediaItemStatistics.MediaItemId)
|
||||
.Map(
|
||||
maybeItem => maybeItem.ToValidation<BaseError>(
|
||||
$"[MediaItem] {refreshMediaItemStatistics.MediaItemId} does not exist."));
|
||||
|
||||
private Validation<BaseError, MediaItem> PathMustExist(MediaItem mediaItem) =>
|
||||
Some(mediaItem)
|
||||
.Filter(item => File.Exists(item.Path))
|
||||
.ToValidation<BaseError>($"[Path] '{mediaItem.Path}' does not exist on the file system");
|
||||
|
||||
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
|
||||
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
|
||||
.FilterT(File.Exists)
|
||||
.Map(
|
||||
ffprobePath =>
|
||||
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system"));
|
||||
|
||||
private Task<Unit> RefreshStatistics(RefreshParameters parameters) =>
|
||||
_localStatisticsProvider.RefreshStatistics(parameters.FFprobePath, parameters.MediaItem).ToUnit();
|
||||
|
||||
private record RefreshParameters(MediaItem MediaItem, string FFprobePath);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using static LanguageExt.Prelude;
|
||||
using System;
|
||||
using System.IO;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems
|
||||
{
|
||||
@@ -12,25 +13,44 @@ namespace ErsatzTV.Application.MediaItems
|
||||
mediaItem.Path);
|
||||
|
||||
internal static MediaItemSearchResultViewModel ProjectToSearchViewModel(MediaItem mediaItem) =>
|
||||
mediaItem switch
|
||||
{
|
||||
TelevisionEpisodeMediaItem e => ProjectToSearchViewModel(e),
|
||||
MovieMediaItem m => ProjectToSearchViewModel(m),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
private static MediaItemSearchResultViewModel ProjectToSearchViewModel(TelevisionEpisodeMediaItem mediaItem) =>
|
||||
new(
|
||||
mediaItem.Id,
|
||||
GetSourceName(mediaItem.Source),
|
||||
mediaItem.Metadata.MediaType.ToString(),
|
||||
"TV Show",
|
||||
GetDisplayTitle(mediaItem),
|
||||
GetDisplayDuration(mediaItem));
|
||||
|
||||
private static MediaItemSearchResultViewModel ProjectToSearchViewModel(MovieMediaItem mediaItem) =>
|
||||
new(
|
||||
mediaItem.Id,
|
||||
GetSourceName(mediaItem.Source),
|
||||
"Movie",
|
||||
GetDisplayTitle(mediaItem),
|
||||
GetDisplayDuration(mediaItem));
|
||||
|
||||
|
||||
private static string GetDisplayTitle(this MediaItem mediaItem) =>
|
||||
mediaItem.Metadata.MediaType == MediaType.TvShow &&
|
||||
Optional(mediaItem.Metadata.SeasonNumber).IsSome &&
|
||||
Optional(mediaItem.Metadata.EpisodeNumber).IsSome
|
||||
? $"{mediaItem.Metadata.Title} s{mediaItem.Metadata.SeasonNumber:00}e{mediaItem.Metadata.EpisodeNumber:00}"
|
||||
: mediaItem.Metadata.Title;
|
||||
private static string GetDisplayTitle(MediaItem mediaItem) =>
|
||||
mediaItem switch
|
||||
{
|
||||
TelevisionEpisodeMediaItem e => e.Metadata != null
|
||||
? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}"
|
||||
: Path.GetFileName(e.Path),
|
||||
MovieMediaItem m => m.Metadata?.Title ?? Path.GetFileName(m.Path),
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
private static string GetDisplayDuration(MediaItem mediaItem) =>
|
||||
string.Format(
|
||||
mediaItem.Metadata.Duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",
|
||||
mediaItem.Metadata.Duration);
|
||||
mediaItem.Statistics.Duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",
|
||||
mediaItem.Statistics.Duration);
|
||||
|
||||
private static string GetSourceName(MediaSource source) =>
|
||||
source switch
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Queries
|
||||
{
|
||||
public record GetAggregateMediaItems
|
||||
(MediaType MediaType, int PageNumber, int PageSize) : IRequest<AggregateMediaItemResults>;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.AggregateModels;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaItems.Queries
|
||||
{
|
||||
public class
|
||||
GetAggregateMediaItemsHandler : IRequestHandler<GetAggregateMediaItems, AggregateMediaItemResults>
|
||||
{
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
public GetAggregateMediaItemsHandler(IMediaItemRepository mediaItemRepository) =>
|
||||
_mediaItemRepository = mediaItemRepository;
|
||||
|
||||
public async Task<AggregateMediaItemResults> Handle(
|
||||
GetAggregateMediaItems request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
int count = await _mediaItemRepository.GetCountByType(request.MediaType);
|
||||
|
||||
IEnumerable<MediaItemSummary> allItems = await _mediaItemRepository.GetPageByType(
|
||||
request.MediaType,
|
||||
request.PageNumber,
|
||||
request.PageSize);
|
||||
|
||||
var results = allItems
|
||||
.Map(
|
||||
s => new AggregateMediaItemViewModel(
|
||||
s.MediaItemId,
|
||||
s.Title,
|
||||
s.Subtitle,
|
||||
s.SortTitle,
|
||||
s.Poster))
|
||||
.ToList();
|
||||
|
||||
return new AggregateMediaItemResults(count, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,10 @@ namespace ErsatzTV.Application.MediaSources.Commands
|
||||
public class
|
||||
DeleteLocalMediaSourceHandler : IRequestHandler<DeleteLocalMediaSource, Either<BaseError, Task>>
|
||||
{
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
|
||||
public DeleteLocalMediaSourceHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IMediaCollectionRepository mediaCollectionRepository)
|
||||
{
|
||||
public DeleteLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) =>
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_mediaCollectionRepository = mediaCollectionRepository;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Task>> Handle(
|
||||
DeleteLocalMediaSource request,
|
||||
@@ -30,14 +24,8 @@ namespace ErsatzTV.Application.MediaSources.Commands
|
||||
.Map(DoDeletion)
|
||||
.ToEither<Task>();
|
||||
|
||||
private async Task DoDeletion(LocalMediaSource mediaSource)
|
||||
{
|
||||
private async Task DoDeletion(LocalMediaSource mediaSource) =>
|
||||
await _mediaSourceRepository.Delete(mediaSource.Id);
|
||||
if (mediaSource.MediaType == MediaType.TvShow)
|
||||
{
|
||||
await _mediaCollectionRepository.DeleteEmptyTelevisionCollections();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, LocalMediaSource>> MediaSourceMustExist(
|
||||
DeleteLocalMediaSource deleteMediaSource) =>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands
|
||||
{
|
||||
public record ScanLocalMediaSource(int MediaSourceId, ScanningMode ScanningMode) :
|
||||
IRequest<Either<BaseError, string>>,
|
||||
public record ScanLocalMediaSource(int MediaSourceId) : IRequest<Either<BaseError, string>>,
|
||||
IBackgroundServiceRequest;
|
||||
}
|
||||
|
||||
@@ -16,33 +16,41 @@ namespace ErsatzTV.Application.MediaSources.Commands
|
||||
{
|
||||
private readonly IConfigElementRepository _configElementRepository;
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
private readonly ILocalMediaScanner _localMediaScanner;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IMovieFolderScanner _movieFolderScanner;
|
||||
private readonly ITelevisionFolderScanner _televisionFolderScanner;
|
||||
|
||||
public ScanLocalMediaSourceHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILocalMediaScanner localMediaScanner,
|
||||
IMovieFolderScanner movieFolderScanner,
|
||||
ITelevisionFolderScanner televisionFolderScanner,
|
||||
IEntityLocker entityLocker)
|
||||
{
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_configElementRepository = configElementRepository;
|
||||
_localMediaScanner = localMediaScanner;
|
||||
_movieFolderScanner = movieFolderScanner;
|
||||
_televisionFolderScanner = televisionFolderScanner;
|
||||
_entityLocker = entityLocker;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, string>>
|
||||
Handle(ScanLocalMediaSource request, CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(parameters => PerformScan(request, parameters).Map(_ => parameters.LocalMediaSource.Folder))
|
||||
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> PerformScan(ScanLocalMediaSource request, RequestParameters parameters)
|
||||
private async Task<Unit> PerformScan(RequestParameters parameters)
|
||||
{
|
||||
await _localMediaScanner.ScanLocalMediaSource(
|
||||
parameters.LocalMediaSource,
|
||||
parameters.FFprobePath,
|
||||
request.ScanningMode);
|
||||
switch (parameters.LocalMediaSource.MediaType)
|
||||
{
|
||||
case MediaType.Movie:
|
||||
await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath);
|
||||
break;
|
||||
case MediaType.TvShow:
|
||||
await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath);
|
||||
break;
|
||||
}
|
||||
|
||||
_entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Movies
|
||||
{
|
||||
internal static class Mapper
|
||||
{
|
||||
internal static MovieViewModel ProjectToViewModel(MovieMediaItem movie) =>
|
||||
new(movie.Metadata.Title, movie.Metadata.Year?.ToString(), movie.Metadata.Plot, movie.Poster);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.Movies
|
||||
{
|
||||
public record MovieViewModel(string Title, string Year, string Plot, string Poster);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Movies.Queries
|
||||
{
|
||||
public record GetMovieById(int Id) : IRequest<Option<MovieViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Movies.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Movies.Queries
|
||||
{
|
||||
public class GetMovieByIdHandler : IRequestHandler<GetMovieById, Option<MovieViewModel>>
|
||||
{
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
|
||||
public GetMovieByIdHandler(IMovieRepository movieRepository) =>
|
||||
_movieRepository = movieRepository;
|
||||
|
||||
public Task<Option<MovieViewModel>> Handle(
|
||||
GetMovieById request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_movieRepository.GetMovie(request.Id).MapT(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using static LanguageExt.Prelude;
|
||||
using System.IO;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Playouts
|
||||
{
|
||||
@@ -22,15 +22,18 @@ namespace ErsatzTV.Application.Playouts
|
||||
new(programSchedule.Id, programSchedule.Name);
|
||||
|
||||
private static string GetDisplayTitle(MediaItem mediaItem) =>
|
||||
mediaItem.Metadata.MediaType == MediaType.TvShow &&
|
||||
Optional(mediaItem.Metadata.SeasonNumber).IsSome &&
|
||||
Optional(mediaItem.Metadata.EpisodeNumber).IsSome
|
||||
? $"{mediaItem.Metadata.Title} s{mediaItem.Metadata.SeasonNumber:00}e{mediaItem.Metadata.EpisodeNumber:00}"
|
||||
: mediaItem.Metadata.Title;
|
||||
mediaItem switch
|
||||
{
|
||||
TelevisionEpisodeMediaItem e => e.Metadata != null
|
||||
? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}"
|
||||
: Path.GetFileName(e.Path),
|
||||
MovieMediaItem m => m.Metadata?.Title ?? Path.GetFileName(m.Path),
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
private static string GetDisplayDuration(MediaItem mediaItem) =>
|
||||
string.Format(
|
||||
mediaItem.Metadata.Duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",
|
||||
mediaItem.Metadata.Duration);
|
||||
mediaItem.Statistics.Duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}",
|
||||
mediaItem.Statistics.Duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
StartType StartType,
|
||||
TimeSpan? StartTime,
|
||||
PlayoutMode PlayoutMode,
|
||||
int MediaCollectionId,
|
||||
ProgramScheduleItemCollectionType CollectionType,
|
||||
int? MediaCollectionId,
|
||||
int? TelevisionShowId,
|
||||
int? TelevisionSeasonId,
|
||||
int? MultipleCount,
|
||||
TimeSpan? PlayoutDuration,
|
||||
bool? OfflineTail) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>, IProgramScheduleItemRequest;
|
||||
|
||||
@@ -6,7 +6,10 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
public interface IProgramScheduleItemRequest
|
||||
{
|
||||
TimeSpan? StartTime { get; }
|
||||
int MediaCollectionId { get; }
|
||||
ProgramScheduleItemCollectionType CollectionType { get; }
|
||||
int? MediaCollectionId { get; }
|
||||
int? TelevisionShowId { get; }
|
||||
int? TelevisionSeasonId { get; }
|
||||
PlayoutMode PlayoutMode { get; }
|
||||
int? MultipleCount { get; }
|
||||
TimeSpan? PlayoutDuration { get; }
|
||||
|
||||
@@ -53,6 +53,40 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
return programSchedule;
|
||||
}
|
||||
|
||||
protected Validation<BaseError, ProgramSchedule> CollectionTypeMustBeValid(
|
||||
IProgramScheduleItemRequest item,
|
||||
ProgramSchedule programSchedule)
|
||||
{
|
||||
switch (item.CollectionType)
|
||||
{
|
||||
case ProgramScheduleItemCollectionType.Collection:
|
||||
if (item.MediaCollectionId is null)
|
||||
{
|
||||
return BaseError.New("[MediaCollection] is required for collection type 'Collection'");
|
||||
}
|
||||
|
||||
break;
|
||||
case ProgramScheduleItemCollectionType.TelevisionShow:
|
||||
if (item.TelevisionShowId is null)
|
||||
{
|
||||
return BaseError.New("[TelevisionShow] is required for collection type 'TelevisionShow'");
|
||||
}
|
||||
|
||||
break;
|
||||
case ProgramScheduleItemCollectionType.TelevisionSeason:
|
||||
if (item.TelevisionSeasonId is null)
|
||||
{
|
||||
return BaseError.New("[TelevisionSeason] is required for collection type 'TelevisionSeason'");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return BaseError.New("[CollectionType] is invalid");
|
||||
}
|
||||
|
||||
return programSchedule;
|
||||
}
|
||||
|
||||
protected ProgramScheduleItem BuildItem(
|
||||
ProgramSchedule programSchedule,
|
||||
int index,
|
||||
@@ -64,21 +98,30 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
ProgramScheduleId = programSchedule.Id,
|
||||
Index = index,
|
||||
StartTime = item.StartTime,
|
||||
MediaCollectionId = item.MediaCollectionId
|
||||
CollectionType = item.CollectionType,
|
||||
MediaCollectionId = item.MediaCollectionId,
|
||||
TelevisionShowId = item.TelevisionShowId,
|
||||
TelevisionSeasonId = item.TelevisionSeasonId
|
||||
},
|
||||
PlayoutMode.One => new ProgramScheduleItemOne
|
||||
{
|
||||
ProgramScheduleId = programSchedule.Id,
|
||||
Index = index,
|
||||
StartTime = item.StartTime,
|
||||
MediaCollectionId = item.MediaCollectionId
|
||||
CollectionType = item.CollectionType,
|
||||
MediaCollectionId = item.MediaCollectionId,
|
||||
TelevisionShowId = item.TelevisionShowId,
|
||||
TelevisionSeasonId = item.TelevisionSeasonId
|
||||
},
|
||||
PlayoutMode.Multiple => new ProgramScheduleItemMultiple
|
||||
{
|
||||
ProgramScheduleId = programSchedule.Id,
|
||||
Index = index,
|
||||
StartTime = item.StartTime,
|
||||
CollectionType = item.CollectionType,
|
||||
MediaCollectionId = item.MediaCollectionId,
|
||||
TelevisionShowId = item.TelevisionShowId,
|
||||
TelevisionSeasonId = item.TelevisionSeasonId,
|
||||
Count = item.MultipleCount.GetValueOrDefault()
|
||||
},
|
||||
PlayoutMode.Duration => new ProgramScheduleItemDuration
|
||||
@@ -86,7 +129,10 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
ProgramScheduleId = programSchedule.Id,
|
||||
Index = index,
|
||||
StartTime = item.StartTime,
|
||||
CollectionType = item.CollectionType,
|
||||
MediaCollectionId = item.MediaCollectionId,
|
||||
TelevisionShowId = item.TelevisionShowId,
|
||||
TelevisionSeasonId = item.TelevisionSeasonId,
|
||||
PlayoutDuration = item.PlayoutDuration.GetValueOrDefault(),
|
||||
OfflineTail = item.OfflineTail.GetValueOrDefault()
|
||||
},
|
||||
|
||||
@@ -12,7 +12,10 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
StartType StartType,
|
||||
TimeSpan? StartTime,
|
||||
PlayoutMode PlayoutMode,
|
||||
int MediaCollectionId,
|
||||
ProgramScheduleItemCollectionType CollectionType,
|
||||
int? MediaCollectionId,
|
||||
int? TelevisionShowId,
|
||||
int? TelevisionSeasonId,
|
||||
int? MultipleCount,
|
||||
TimeSpan? PlayoutDuration,
|
||||
bool? OfflineTail) : IProgramScheduleItemRequest;
|
||||
|
||||
+8
-1
@@ -55,12 +55,19 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
|
||||
|
||||
private Task<Validation<BaseError, ProgramSchedule>> Validate(ReplaceProgramScheduleItems request) =>
|
||||
ProgramScheduleMustExist(request.ProgramScheduleId)
|
||||
.BindT(programSchedule => PlayoutModesMustBeValid(request, programSchedule));
|
||||
.BindT(programSchedule => PlayoutModesMustBeValid(request, programSchedule))
|
||||
.BindT(programSchedule => CollectionTypesMustBeValid(request, programSchedule));
|
||||
|
||||
private Validation<BaseError, ProgramSchedule> PlayoutModesMustBeValid(
|
||||
ReplaceProgramScheduleItems request,
|
||||
ProgramSchedule programSchedule) =>
|
||||
request.Items.Map(item => PlayoutModeMustBeValid(item, programSchedule)).Sequence()
|
||||
.Map(_ => programSchedule);
|
||||
|
||||
private Validation<BaseError, ProgramSchedule> CollectionTypesMustBeValid(
|
||||
ReplaceProgramScheduleItems request,
|
||||
ProgramSchedule programSchedule) =>
|
||||
request.Items.Map(item => CollectionTypeMustBeValid(item, programSchedule)).Sequence()
|
||||
.Map(_ => programSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,16 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
duration.Index,
|
||||
duration.StartType,
|
||||
duration.StartTime,
|
||||
MediaCollections.Mapper.ProjectToViewModel(duration.MediaCollection),
|
||||
duration.CollectionType,
|
||||
duration.MediaCollection != null
|
||||
? MediaCollections.Mapper.ProjectToViewModel(duration.MediaCollection)
|
||||
: null,
|
||||
duration.TelevisionShow != null
|
||||
? Television.Mapper.ProjectToViewModel(duration.TelevisionShow)
|
||||
: null,
|
||||
duration.TelevisionSeason != null
|
||||
? Television.Mapper.ProjectToViewModel(duration.TelevisionSeason)
|
||||
: null,
|
||||
duration.PlayoutDuration,
|
||||
duration.OfflineTail),
|
||||
ProgramScheduleItemFlood flood =>
|
||||
@@ -26,14 +35,32 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
flood.Index,
|
||||
flood.StartType,
|
||||
flood.StartTime,
|
||||
MediaCollections.Mapper.ProjectToViewModel(flood.MediaCollection)),
|
||||
flood.CollectionType,
|
||||
flood.MediaCollection != null
|
||||
? MediaCollections.Mapper.ProjectToViewModel(flood.MediaCollection)
|
||||
: null,
|
||||
flood.TelevisionShow != null
|
||||
? Television.Mapper.ProjectToViewModel(flood.TelevisionShow)
|
||||
: null,
|
||||
flood.TelevisionSeason != null
|
||||
? Television.Mapper.ProjectToViewModel(flood.TelevisionSeason)
|
||||
: null),
|
||||
ProgramScheduleItemMultiple multiple =>
|
||||
new ProgramScheduleItemMultipleViewModel(
|
||||
multiple.Id,
|
||||
multiple.Index,
|
||||
multiple.StartType,
|
||||
multiple.StartTime,
|
||||
MediaCollections.Mapper.ProjectToViewModel(multiple.MediaCollection),
|
||||
multiple.CollectionType,
|
||||
multiple.MediaCollection != null
|
||||
? MediaCollections.Mapper.ProjectToViewModel(multiple.MediaCollection)
|
||||
: null,
|
||||
multiple.TelevisionShow != null
|
||||
? Television.Mapper.ProjectToViewModel(multiple.TelevisionShow)
|
||||
: null,
|
||||
multiple.TelevisionSeason != null
|
||||
? Television.Mapper.ProjectToViewModel(multiple.TelevisionSeason)
|
||||
: null,
|
||||
multiple.Count),
|
||||
ProgramScheduleItemOne one =>
|
||||
new ProgramScheduleItemOneViewModel(
|
||||
@@ -41,7 +68,14 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
one.Index,
|
||||
one.StartType,
|
||||
one.StartTime,
|
||||
MediaCollections.Mapper.ProjectToViewModel(one.MediaCollection)),
|
||||
one.CollectionType,
|
||||
one.MediaCollection != null
|
||||
? MediaCollections.Mapper.ProjectToViewModel(one.MediaCollection)
|
||||
: null,
|
||||
one.TelevisionShow != null ? Television.Mapper.ProjectToViewModel(one.TelevisionShow) : null,
|
||||
one.TelevisionSeason != null
|
||||
? Television.Mapper.ProjectToViewModel(one.TelevisionSeason)
|
||||
: null),
|
||||
_ => throw new NotSupportedException(
|
||||
$"Unsupported program schedule item type {programScheduleItem.GetType().Name}")
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.Television;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.ProgramSchedules
|
||||
@@ -11,7 +12,10 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
int index,
|
||||
StartType startType,
|
||||
TimeSpan? startTime,
|
||||
ProgramScheduleItemCollectionType collectionType,
|
||||
MediaCollectionViewModel mediaCollection,
|
||||
TelevisionShowViewModel televisionShow,
|
||||
TelevisionSeasonViewModel televisionSeason,
|
||||
TimeSpan playoutDuration,
|
||||
bool offlineTail) : base(
|
||||
id,
|
||||
@@ -19,7 +23,10 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
startType,
|
||||
startTime,
|
||||
PlayoutMode.Duration,
|
||||
mediaCollection)
|
||||
collectionType,
|
||||
mediaCollection,
|
||||
televisionShow,
|
||||
televisionSeason)
|
||||
{
|
||||
PlayoutDuration = playoutDuration;
|
||||
OfflineTail = offlineTail;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.Television;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.ProgramSchedules
|
||||
@@ -11,13 +12,19 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
int index,
|
||||
StartType startType,
|
||||
TimeSpan? startTime,
|
||||
MediaCollectionViewModel mediaCollection) : base(
|
||||
ProgramScheduleItemCollectionType collectionType,
|
||||
MediaCollectionViewModel mediaCollection,
|
||||
TelevisionShowViewModel televisionShow,
|
||||
TelevisionSeasonViewModel televisionSeason) : base(
|
||||
id,
|
||||
index,
|
||||
startType,
|
||||
startTime,
|
||||
PlayoutMode.Flood,
|
||||
mediaCollection)
|
||||
collectionType,
|
||||
mediaCollection,
|
||||
televisionShow,
|
||||
televisionSeason)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.Television;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.ProgramSchedules
|
||||
@@ -11,14 +12,20 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
int index,
|
||||
StartType startType,
|
||||
TimeSpan? startTime,
|
||||
ProgramScheduleItemCollectionType collectionType,
|
||||
MediaCollectionViewModel mediaCollection,
|
||||
TelevisionShowViewModel televisionShow,
|
||||
TelevisionSeasonViewModel televisionSeason,
|
||||
int count) : base(
|
||||
id,
|
||||
index,
|
||||
startType,
|
||||
startTime,
|
||||
PlayoutMode.Multiple,
|
||||
mediaCollection) =>
|
||||
collectionType,
|
||||
mediaCollection,
|
||||
televisionShow,
|
||||
televisionSeason) =>
|
||||
Count = count;
|
||||
|
||||
public int Count { get; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.Television;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.ProgramSchedules
|
||||
@@ -11,13 +12,19 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
int index,
|
||||
StartType startType,
|
||||
TimeSpan? startTime,
|
||||
MediaCollectionViewModel mediaCollection) : base(
|
||||
ProgramScheduleItemCollectionType collectionType,
|
||||
MediaCollectionViewModel mediaCollection,
|
||||
TelevisionShowViewModel televisionShow,
|
||||
TelevisionSeasonViewModel televisionSeason) : base(
|
||||
id,
|
||||
index,
|
||||
startType,
|
||||
startTime,
|
||||
PlayoutMode.One,
|
||||
mediaCollection)
|
||||
collectionType,
|
||||
mediaCollection,
|
||||
televisionShow,
|
||||
televisionSeason)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using ErsatzTV.Application.MediaCollections;
|
||||
using ErsatzTV.Application.Television;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.ProgramSchedules
|
||||
@@ -10,5 +11,18 @@ namespace ErsatzTV.Application.ProgramSchedules
|
||||
StartType StartType,
|
||||
TimeSpan? StartTime,
|
||||
PlayoutMode PlayoutMode,
|
||||
MediaCollectionViewModel MediaCollection);
|
||||
ProgramScheduleItemCollectionType CollectionType,
|
||||
MediaCollectionViewModel MediaCollection,
|
||||
TelevisionShowViewModel TelevisionShow,
|
||||
TelevisionSeasonViewModel TelevisionSeason)
|
||||
{
|
||||
public string Name => CollectionType switch
|
||||
{
|
||||
ProgramScheduleItemCollectionType.Collection => MediaCollection?.Name,
|
||||
ProgramScheduleItemCollectionType.TelevisionShow => $"{TelevisionShow?.Title} ({TelevisionShow?.Year})",
|
||||
ProgramScheduleItemCollectionType.TelevisionSeason =>
|
||||
$"{TelevisionSeason?.Title} ({TelevisionSeason?.Plot})",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Television
|
||||
{
|
||||
internal static class Mapper
|
||||
{
|
||||
internal static TelevisionShowViewModel ProjectToViewModel(TelevisionShow show) =>
|
||||
new(show.Id, show.Metadata.Title, show.Metadata.Year?.ToString(), show.Metadata.Plot, show.Poster);
|
||||
|
||||
internal static TelevisionSeasonViewModel ProjectToViewModel(TelevisionSeason season) =>
|
||||
new(
|
||||
season.Id,
|
||||
season.TelevisionShowId,
|
||||
season.TelevisionShow.Metadata.Title,
|
||||
season.TelevisionShow.Metadata.Year?.ToString(),
|
||||
season.Number == 0 ? "Specials" : $"Season {season.Number}",
|
||||
season.Poster);
|
||||
|
||||
internal static TelevisionEpisodeViewModel ProjectToViewModel(TelevisionEpisodeMediaItem episode) =>
|
||||
new(
|
||||
episode.Season.TelevisionShowId,
|
||||
episode.SeasonId,
|
||||
episode.Metadata.Episode,
|
||||
episode.Metadata.Title,
|
||||
episode.Metadata.Plot,
|
||||
episode.Poster);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public record GetAllTelevisionSeasons : IRequest<List<TelevisionSeasonViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Television.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public class
|
||||
GetAllTelevisionSeasonsHandler : IRequestHandler<GetAllTelevisionSeasons, List<TelevisionSeasonViewModel>>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetAllTelevisionSeasonsHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public Task<List<TelevisionSeasonViewModel>> Handle(
|
||||
GetAllTelevisionSeasons request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_televisionRepository.GetAllSeasons().Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public record GetAllTelevisionShows : IRequest<List<TelevisionShowViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Television.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public class GetAllTelevisionShowsHandler : IRequestHandler<GetAllTelevisionShows, List<TelevisionShowViewModel>>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetAllTelevisionShowsHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public Task<List<TelevisionShowViewModel>> Handle(
|
||||
GetAllTelevisionShows request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_televisionRepository.GetAllShows().Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public record GetTelevisionEpisodeById(int EpisodeId) : IRequest<Option<TelevisionEpisodeViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Television.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public class
|
||||
GetTelevisionEpisodeByIdHandler : IRequestHandler<GetTelevisionEpisodeById, Option<TelevisionEpisodeViewModel>>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionEpisodeByIdHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public Task<Option<TelevisionEpisodeViewModel>> Handle(
|
||||
GetTelevisionEpisodeById request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_televisionRepository.GetEpisode(request.EpisodeId)
|
||||
.MapT(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public record GetTelevisionSeasonById(int SeasonId) : IRequest<Option<TelevisionSeasonViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Television.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public class
|
||||
GetTelevisionSeasonByIdHandler : IRequestHandler<GetTelevisionSeasonById, Option<TelevisionSeasonViewModel>>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionSeasonByIdHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public Task<Option<TelevisionSeasonViewModel>> Handle(
|
||||
GetTelevisionSeasonById request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_televisionRepository.GetSeason(request.SeasonId)
|
||||
.MapT(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public record GetTelevisionShowById(int Id) : IRequest<Option<TelevisionShowViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Television.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Television.Queries
|
||||
{
|
||||
public class GetTelevisionShowByIdHandler : IRequestHandler<GetTelevisionShowById, Option<TelevisionShowViewModel>>
|
||||
{
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public GetTelevisionShowByIdHandler(ITelevisionRepository televisionRepository) =>
|
||||
_televisionRepository = televisionRepository;
|
||||
|
||||
public Task<Option<TelevisionShowViewModel>> Handle(
|
||||
GetTelevisionShowById request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_televisionRepository.GetShow(request.Id)
|
||||
.MapT(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace ErsatzTV.Application.Television
|
||||
{
|
||||
public record TelevisionEpisodeViewModel(
|
||||
int ShowId,
|
||||
int SeasonId,
|
||||
int Episode,
|
||||
string Title,
|
||||
string Plot,
|
||||
string Poster);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.Television
|
||||
{
|
||||
public record TelevisionSeasonViewModel(int Id, int ShowId, string Title, string Year, string Plot, string Poster);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.Television
|
||||
{
|
||||
public record TelevisionShowViewModel(int Id, string Title, string Year, string Plot, string Poster);
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using ErsatzTV.Api.Sdk.Api;
|
||||
using ErsatzTV.Api.Sdk.Model;
|
||||
using LanguageExt;
|
||||
using LanguageExt.Common;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.CommandLine.Commands.MediaCollections
|
||||
{
|
||||
[Command("collection add-items", Description = "Ensure media collection exists and contains requested media items")]
|
||||
public class MediaCollectionMediaItemsCommand : MediaItemCommandBase
|
||||
{
|
||||
private readonly ILogger<MediaCollectionMediaItemsCommand> _logger;
|
||||
private readonly string _serverUrl;
|
||||
|
||||
public MediaCollectionMediaItemsCommand(
|
||||
IConfiguration configuration,
|
||||
ILogger<MediaCollectionMediaItemsCommand> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_serverUrl = configuration["ServerUrl"];
|
||||
}
|
||||
|
||||
[CommandParameter(0, Name = "collection-name", Description = "The name of the media collection")]
|
||||
public string Name { get; set; }
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
try
|
||||
{
|
||||
CancellationToken cancellationToken = console.GetCancellationToken();
|
||||
|
||||
Either<Error, List<string>> maybeFileNames = await GetFileNames();
|
||||
await maybeFileNames.Match(
|
||||
allFiles => SynchronizeMediaItemsToCollection(cancellationToken, allFiles),
|
||||
error =>
|
||||
{
|
||||
_logger.LogError("{Error}", error.Message);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unable to synchronize media items to media collection: {Error}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SynchronizeMediaItemsToCollection(CancellationToken cancellationToken, List<string> allFiles)
|
||||
{
|
||||
Either<Error, Unit> result = await GetMediaSourceIdAsync(cancellationToken)
|
||||
.BindAsync(mediaSourceId => SynchronizeMediaItemsAsync(mediaSourceId, allFiles, cancellationToken))
|
||||
.BindAsync(mediaItemIds => SynchronizeMediaItemsToCollectionAsync(mediaItemIds, cancellationToken));
|
||||
|
||||
result.Match(
|
||||
_ => _logger.LogInformation(
|
||||
"Successfully synchronized {Count} media items to media collection {MediaCollection}",
|
||||
allFiles.Count,
|
||||
Name),
|
||||
error => _logger.LogError(
|
||||
"Unable to synchronize media items to media collection: {Error}",
|
||||
error.Message));
|
||||
}
|
||||
|
||||
private async Task<Either<Error, int>> GetMediaSourceIdAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaSourcesApi = new MediaSourcesApi(_serverUrl);
|
||||
List<MediaSourceViewModel> allMediaSources =
|
||||
await mediaSourcesApi.ApiMediaSourcesGetAsync(cancellationToken);
|
||||
Option<MediaSourceViewModel> maybeLocalMediaSource =
|
||||
allMediaSources.SingleOrDefault(cs => cs.SourceType == MediaSourceType.Local);
|
||||
return maybeLocalMediaSource.Match<Either<Error, int>>(
|
||||
mediaSource => mediaSource.Id,
|
||||
() => Error.New("Unable to find local media source"));
|
||||
}
|
||||
|
||||
private async Task<Either<Error, List<int>>> SynchronizeMediaItemsAsync(
|
||||
int mediaSourceId,
|
||||
ICollection<string> fileNames,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaItemsApi = new MediaItemsApi(_serverUrl);
|
||||
List<MediaItemViewModel> allMediaItems = await mediaItemsApi.ApiMediaItemsGetAsync(cancellationToken);
|
||||
var missingMediaItems = fileNames.Where(f => allMediaItems.All(c => c.Path != f))
|
||||
.Map(f => new CreateMediaItem(mediaSourceId, f))
|
||||
.ToList();
|
||||
|
||||
var addedIds = new List<int>();
|
||||
foreach (CreateMediaItem mediaItem in missingMediaItems)
|
||||
{
|
||||
_logger.LogInformation("Adding media item {Path}", mediaItem.Path);
|
||||
addedIds.Add(await mediaItemsApi.ApiMediaItemsPostAsync(mediaItem, cancellationToken).Map(vm => vm.Id));
|
||||
}
|
||||
|
||||
IEnumerable<int> knownIds = allMediaItems.Where(c => fileNames.Contains(c.Path)).Map(c => c.Id);
|
||||
|
||||
return knownIds.Concat(addedIds).ToList();
|
||||
}
|
||||
|
||||
private async Task<Either<Error, Unit>> SynchronizeMediaItemsToCollectionAsync(
|
||||
List<int> mediaItemIds,
|
||||
CancellationToken cancellationToken) =>
|
||||
await EnsureMediaCollectionExistsAsync(cancellationToken)
|
||||
.BindAsync(
|
||||
mediaSourceId => SynchronizeMediaCollectionAsync(mediaSourceId, mediaItemIds, cancellationToken));
|
||||
|
||||
private async Task<Either<Error, int>> EnsureMediaCollectionExistsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaCollectionsApi = new MediaCollectionsApi(_serverUrl);
|
||||
Option<MediaCollectionViewModel> maybeExisting = await mediaCollectionsApi
|
||||
.ApiMediaCollectionsGetAsync(cancellationToken)
|
||||
.Map(list => list.SingleOrDefault(mc => mc.Name == Name));
|
||||
return await maybeExisting.Match(
|
||||
existing => Task.FromResult(existing.Id),
|
||||
async () =>
|
||||
{
|
||||
var data = new CreateSimpleMediaCollection(Name);
|
||||
return await mediaCollectionsApi.ApiMediaCollectionsPostAsync(data, cancellationToken)
|
||||
.Map(vm => vm.Id);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<Either<Error, Unit>> SynchronizeMediaCollectionAsync(
|
||||
int mediaCollectionId,
|
||||
List<int> mediaItemIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaCollectionsApi = new MediaCollectionsApi(_serverUrl);
|
||||
await mediaCollectionsApi.ApiMediaCollectionsIdItemsPutAsync(
|
||||
mediaCollectionId,
|
||||
mediaItemIds,
|
||||
cancellationToken);
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using LanguageExt;
|
||||
using LanguageExt.Common;
|
||||
|
||||
namespace ErsatzTV.CommandLine.Commands
|
||||
{
|
||||
public abstract class MediaItemCommandBase : ICommand
|
||||
{
|
||||
[CommandOption("folder", 'f', Description = "Folder to search for media items")]
|
||||
public string Folder { get; set; }
|
||||
|
||||
[CommandOption("pattern", 'p', Description = "File search pattern")]
|
||||
public string SearchPattern { get; set; }
|
||||
|
||||
public abstract ValueTask ExecuteAsync(IConsole console);
|
||||
|
||||
protected async Task<Either<Error, List<string>>> GetFileNames()
|
||||
{
|
||||
if (Console.IsInputRedirected)
|
||||
{
|
||||
await using Stream standardInput = Console.OpenStandardInput();
|
||||
using var streamReader = new StreamReader(standardInput);
|
||||
string input = await streamReader.ReadToEndAsync();
|
||||
return input.Trim().Split("\n").Map(s => s.Trim()).ToList();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Folder) || string.IsNullOrWhiteSpace(SearchPattern))
|
||||
{
|
||||
return Error.New(
|
||||
"--folder and --pattern are required when file names are not passed on standard input");
|
||||
}
|
||||
|
||||
return Directory.GetFiles(Folder, SearchPattern, SearchOption.AllDirectories).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using ErsatzTV.Api.Sdk.Api;
|
||||
using ErsatzTV.Api.Sdk.Model;
|
||||
using LanguageExt;
|
||||
using LanguageExt.Common;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.CommandLine.Commands
|
||||
{
|
||||
[Command("items", Description = "Ensure media items exist")]
|
||||
public class MediaItemsCommand : MediaItemCommandBase
|
||||
{
|
||||
private readonly ILogger<MediaItemsCommand> _logger;
|
||||
private readonly string _serverUrl;
|
||||
|
||||
public MediaItemsCommand(IConfiguration configuration, ILogger<MediaItemsCommand> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_serverUrl = configuration["ServerUrl"];
|
||||
}
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
try
|
||||
{
|
||||
CancellationToken cancellationToken = console.GetCancellationToken();
|
||||
|
||||
Either<Error, List<string>> maybeFileNames = await GetFileNames();
|
||||
await maybeFileNames.Match(
|
||||
allFiles => SynchronizeMediaItems(cancellationToken, allFiles),
|
||||
error =>
|
||||
{
|
||||
_logger.LogError("{Error}", error.Message);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unable to synchronize media items: {Error}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SynchronizeMediaItems(CancellationToken cancellationToken, List<string> allFiles)
|
||||
{
|
||||
Either<Error, Unit> result = await GetMediaSourceId(cancellationToken)
|
||||
.BindAsync(
|
||||
contentSourceId => PostMediaItems(
|
||||
contentSourceId,
|
||||
allFiles,
|
||||
cancellationToken));
|
||||
|
||||
result.Match(
|
||||
_ => _logger.LogInformation(
|
||||
"Successfully synchronized {Count} media items",
|
||||
allFiles.Count),
|
||||
error => _logger.LogError("Unable to synchronize media items: {Error}", error.Message));
|
||||
}
|
||||
|
||||
private async Task<Either<Error, int>> GetMediaSourceId(CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaSourcesApi = new MediaSourcesApi(_serverUrl);
|
||||
List<MediaSourceViewModel> allMediaSources =
|
||||
await mediaSourcesApi.ApiMediaSourcesGetAsync(cancellationToken);
|
||||
Option<MediaSourceViewModel> maybeLocalMediaSource =
|
||||
allMediaSources.SingleOrDefault(cs => cs.SourceType == MediaSourceType.Local);
|
||||
return maybeLocalMediaSource.Match<Either<Error, int>>(
|
||||
mediaSource => mediaSource.Id,
|
||||
() => Error.New("Unable to find local media source"));
|
||||
}
|
||||
|
||||
private async Task<Either<Error, Unit>> PostMediaItems(
|
||||
int mediaSourceId,
|
||||
ICollection<string> fileNames,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaItemsApi = new MediaItemsApi(_serverUrl);
|
||||
List<MediaItemViewModel> allContent = await mediaItemsApi.ApiMediaItemsGetAsync(cancellationToken);
|
||||
var missingMediaItems = fileNames.Where(f => allContent.All(c => c.Path != f))
|
||||
.Map(f => new CreateMediaItem(mediaSourceId, f))
|
||||
.ToList();
|
||||
|
||||
foreach (CreateMediaItem mediaItem in missingMediaItems)
|
||||
{
|
||||
_logger.LogInformation("Adding media item {Path}", mediaItem.Path);
|
||||
await mediaItemsApi.ApiMediaItemsPostAsync(mediaItem, cancellationToken);
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
{
|
||||
MediaItem = new MediaItem
|
||||
{
|
||||
Metadata = new MediaMetadata()
|
||||
Statistics = new MediaItemStatistics()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,9 +175,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -198,9 +198,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -221,9 +221,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -245,9 +245,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -269,9 +269,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.HttpLiveStreaming,
|
||||
@@ -295,9 +295,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -323,10 +323,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "mpeg2video";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "mpeg2video";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -352,10 +352,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "mpeg2video";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "mpeg2video";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.HttpLiveStreaming,
|
||||
@@ -380,10 +380,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "libx264";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "libx264";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -409,10 +409,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "mpeg2video";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "mpeg2video";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -437,9 +437,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -464,10 +464,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "mpeg2video";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "mpeg2video";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -492,9 +492,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1918;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.Width = 1918;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -520,10 +520,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.Width = 1920;
|
||||
playoutItem.MediaItem.Metadata.Height = 1080;
|
||||
playoutItem.MediaItem.Metadata.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Metadata.VideoCodec = "mpeg2video";
|
||||
playoutItem.MediaItem.Statistics.Width = 1920;
|
||||
playoutItem.MediaItem.Statistics.Height = 1080;
|
||||
playoutItem.MediaItem.Statistics.SampleAspectRatio = "1:1"; // not anamorphic
|
||||
playoutItem.MediaItem.Statistics.VideoCodec = "mpeg2video";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -546,7 +546,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "aac";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "aac";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -567,7 +567,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -588,7 +588,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -609,7 +609,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.HttpLiveStreaming,
|
||||
@@ -630,7 +630,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -651,7 +651,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -674,7 +674,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -697,7 +697,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -719,7 +719,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
@@ -741,7 +741,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
|
||||
};
|
||||
|
||||
PlayoutItem playoutItem = EmptyPlayoutItem();
|
||||
playoutItem.MediaItem.Metadata.AudioCodec = "ac3";
|
||||
playoutItem.MediaItem.Statistics.AudioCodec = "ac3";
|
||||
|
||||
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
|
||||
StreamingMode.TransportStream,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Fakes
|
||||
{
|
||||
public record FakeFileEntry(string Path)
|
||||
{
|
||||
public DateTime LastWriteTime { get; set; } = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user