mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* 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 screenshotspull/28/head v0.0.10-prealpha
259 changed files with 13476 additions and 4754 deletions
@ -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>>; |
|
||||||
} |
|
@ -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>>; |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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>>; |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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>>; |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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>>; |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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>>>; |
|
||||||
} |
|
@ -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,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()); |
|
||||||
} |
|
||||||
} |
|
@ -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>>>>; |
|
||||||
} |
|
@ -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,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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,11 +1,9 @@ |
|||||||
using ErsatzTV.Core; |
using ErsatzTV.Core; |
||||||
using ErsatzTV.Core.Metadata; |
|
||||||
using LanguageExt; |
using LanguageExt; |
||||||
using MediatR; |
using MediatR; |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources.Commands |
namespace ErsatzTV.Application.MediaSources.Commands |
||||||
{ |
{ |
||||||
public record ScanLocalMediaSource(int MediaSourceId, ScanningMode ScanningMode) : |
public record ScanLocalMediaSource(int MediaSourceId) : IRequest<Either<BaseError, string>>, |
||||||
IRequest<Either<BaseError, string>>, |
|
||||||
IBackgroundServiceRequest; |
IBackgroundServiceRequest; |
||||||
} |
} |
||||||
|
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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
Loading…
Reference in new issue