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 @@
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application |
||||
{ |
||||
public interface IMediaCard |
||||
{ |
||||
string Title { get; } |
||||
string SortTitle { get; } |
||||
string Subtitle { get; } |
||||
} |
||||
} |
@ -0,0 +1,60 @@
@@ -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 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record MediaCardViewModel(string Title, string Subtitle, string SortTitle, string Poster); |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record MovieCardResultsViewModel(int Count, List<MovieCardViewModel> Cards); |
||||
} |
@ -0,0 +1,11 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public record GetMovieCards(int PageNumber, int PageSize) : IRequest<MovieCardResultsViewModel>; |
||||
} |
@ -0,0 +1,32 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public record GetTelevisionShowCards(int PageNumber, int PageSize) : IRequest<TelevisionShowCardResultsViewModel>; |
||||
} |
@ -0,0 +1,33 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record TelevisionEpisodeCardResultsViewModel(int Count, List<TelevisionEpisodeCardViewModel> Cards); |
||||
} |
@ -0,0 +1,21 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record TelevisionSeasonCardResultsViewModel(int Count, List<TelevisionSeasonCardViewModel> Cards); |
||||
} |
@ -0,0 +1,19 @@
@@ -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 @@
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record TelevisionShowCardResultsViewModel(int Count, List<TelevisionShowCardViewModel> Cards); |
||||
} |
@ -0,0 +1,11 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,6 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaItems |
||||
{ |
||||
public record AggregateMediaItemResults(int Count, List<AggregateMediaItemViewModel> DataPage); |
||||
} |
@ -1,9 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands |
||||
{ |
||||
public record RefreshMediaItemCollections : RefreshMediaItem |
||||
{ |
||||
public RefreshMediaItemCollections(int mediaItemId) : base(mediaItemId) |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -1,46 +0,0 @@
@@ -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 @@
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands |
||||
{ |
||||
public record RefreshMediaItemMetadata : RefreshMediaItem |
||||
{ |
||||
public RefreshMediaItemMetadata(int mediaItemId) : base(mediaItemId) |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -1,53 +0,0 @@
@@ -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 @@
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands |
||||
{ |
||||
public record RefreshMediaItemPoster : RefreshMediaItem |
||||
{ |
||||
public RefreshMediaItemPoster(int mediaItemId) : base(mediaItemId) |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -1,45 +0,0 @@
@@ -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 @@
@@ -1,9 +0,0 @@
|
||||
namespace ErsatzTV.Application.MediaItems.Commands |
||||
{ |
||||
public record RefreshMediaItemStatistics : RefreshMediaItem |
||||
{ |
||||
public RefreshMediaItemStatistics(int mediaItemId) : base(mediaItemId) |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -1,65 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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; |
||||
} |
||||
|
@ -0,0 +1,10 @@
@@ -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 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.Movies |
||||
{ |
||||
public record MovieViewModel(string Title, string Year, string Plot, string Poster); |
||||
} |
@ -0,0 +1,7 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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