mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* starting database redesign * set season and episode numbers * use datetimes in db (utc); update movie metadata * get movie cards from new table * copy show/episode metadata * remove old movie metadata type * rename new movie metadata type * code cleanup * start to remove old television classes * remove old television tables from database * fix playout building * fix collection views * fix show/season views * clean up movie metadata table * fix scanner tests * add libraries ui * code cleanup * fix movie scanning/metadata * add library scan button to ui * delete library path from ui * temp disable movie scanning * remove orphan media items and prevent duplicate paths * attach artwork to metadata * fix split show/season display * fix television artwork * store year distinct from release date * fix collections ui * code cleanup * add library paths from ui * fix adding to collections from ui * fix schedule items loading * schedule editing works again * remove some todos * more cleanup * fix unit tests * fix episode sorting * fix deleting show library paths * remove unused class * fix playout list in ui * fix log viewer * start to use version/file instead of statistics * clean up old columns * fix playout display (time zone) * fix playback * fix channel guide time zone * cascade more deletes * fix compiler warnings * fix adding new seasons * use artwork for channel logo * clean cache folder on startup (move channel logos, delete everything else) * log database migration * update homepage docs for libraries * fix adding new channel with logo * fix episode numbers in epgpull/33/head
392 changed files with 51994 additions and 3965 deletions
@ -1,10 +1,22 @@
@@ -1,10 +1,22 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Application.Channels |
||||
{ |
||||
internal static class Mapper |
||||
{ |
||||
internal static ChannelViewModel ProjectToViewModel(Channel channel) => |
||||
new(channel.Id, channel.Number, channel.Name, channel.FFmpegProfileId, channel.Logo, channel.StreamingMode); |
||||
new( |
||||
channel.Id, |
||||
channel.Number, |
||||
channel.Name, |
||||
channel.FFmpegProfileId, |
||||
GetLogo(channel), |
||||
channel.StreamingMode); |
||||
|
||||
private static string GetLogo(Channel channel) => |
||||
Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo)) |
||||
.Match(a => a.Path, string.Empty); |
||||
} |
||||
} |
||||
|
@ -1,9 +1,10 @@
@@ -1,9 +1,10 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Images.Commands |
||||
{ |
||||
// ReSharper disable once SuggestBaseTypeForParameter
|
||||
public record SaveImageToDisk(byte[] Buffer) : IRequest<Either<BaseError, string>>; |
||||
public record SaveArtworkToDisk(byte[] Buffer, ArtworkKind ArtworkKind) : IRequest<Either<BaseError, string>>; |
||||
} |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Interfaces.Images; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Images.Commands |
||||
{ |
||||
public class SaveArtworkToDiskHandler : IRequestHandler<SaveArtworkToDisk, Either<BaseError, string>> |
||||
{ |
||||
private readonly IImageCache _imageCache; |
||||
|
||||
public SaveArtworkToDiskHandler(IImageCache imageCache) => _imageCache = imageCache; |
||||
|
||||
public Task<Either<BaseError, string>> Handle(SaveArtworkToDisk request, CancellationToken cancellationToken) => |
||||
_imageCache.SaveArtworkToCache(request.Buffer, request.ArtworkKind); |
||||
} |
||||
} |
@ -1,20 +0,0 @@
@@ -1,20 +0,0 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Interfaces.Images; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Images.Commands |
||||
{ |
||||
public class SaveImageToDiskHandler : IRequestHandler<SaveImageToDisk, Either<BaseError, string>> |
||||
{ |
||||
private readonly IImageCache _imageCache; |
||||
|
||||
public SaveImageToDiskHandler(IImageCache imageCache) => _imageCache = imageCache; |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
SaveImageToDisk request, |
||||
CancellationToken cancellationToken) => _imageCache.SaveImage(request.Buffer); |
||||
} |
||||
} |
@ -1,8 +1,10 @@
@@ -1,8 +1,10 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Images.Queries |
||||
{ |
||||
public record GetImageContents(string FileName) : IRequest<Either<BaseError, ImageViewModel>>; |
||||
public record GetImageContents |
||||
(string FileName, ArtworkKind ArtworkKind, int? MaxHeight = null) : IRequest<Either<BaseError, ImageViewModel>>; |
||||
} |
||||
|
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Commands |
||||
{ |
||||
public record CreateLocalLibraryPath |
||||
(int LibraryId, string Path) : IRequest<Either<BaseError, LocalLibraryPathViewModel>>; |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
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 MediatR; |
||||
using static LanguageExt.Prelude; |
||||
using static ErsatzTV.Application.Libraries.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Commands |
||||
{ |
||||
public class CreateLocalLibraryPathHandler : IRequestHandler<CreateLocalLibraryPath, |
||||
Either<BaseError, LocalLibraryPathViewModel>> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public CreateLocalLibraryPathHandler(ILibraryRepository mediaSourceRepository) => |
||||
_libraryRepository = mediaSourceRepository; |
||||
|
||||
public Task<Either<BaseError, LocalLibraryPathViewModel>> Handle( |
||||
CreateLocalLibraryPath request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request).MapT(PersistLocalLibraryPath).Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<LocalLibraryPathViewModel> PersistLocalLibraryPath(LibraryPath p) => |
||||
_libraryRepository.Add(p).Map(ProjectToViewModel); |
||||
|
||||
private Task<Validation<BaseError, LibraryPath>> Validate(CreateLocalLibraryPath request) => |
||||
ValidateFolder(request) |
||||
.MapT( |
||||
folder => |
||||
new LibraryPath |
||||
{ |
||||
LibraryId = request.LibraryId, |
||||
Path = folder |
||||
}); |
||||
|
||||
private async Task<Validation<BaseError, string>> ValidateFolder(CreateLocalLibraryPath request) |
||||
{ |
||||
List<string> allPaths = await _libraryRepository.GetLocalPaths(request.LibraryId) |
||||
.Map(list => list.Map(c => c.Path).ToList()); |
||||
|
||||
|
||||
return Optional(request.Path) |
||||
.Filter(folder => allPaths.ForAll(f => !AreSubPaths(f, folder))) |
||||
.ToValidation<BaseError>("Path must not belong to another library path"); |
||||
} |
||||
|
||||
private static bool AreSubPaths(string path1, string path2) |
||||
{ |
||||
string one = path1 + Path.DirectorySeparatorChar; |
||||
string two = path2 + Path.DirectorySeparatorChar; |
||||
return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) || |
||||
two.StartsWith(one, StringComparison.OrdinalIgnoreCase); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Commands |
||||
{ |
||||
public record DeleteLocalLibraryPath(int LocalLibraryPathId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Commands |
||||
{ |
||||
public class |
||||
DeleteLocalLibraryPathHandler : MediatR.IRequestHandler<DeleteLocalLibraryPath, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public DeleteLocalLibraryPathHandler(ILibraryRepository libraryRepository) => |
||||
_libraryRepository = libraryRepository; |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
DeleteLocalLibraryPath request, |
||||
CancellationToken cancellationToken) => |
||||
MediaSourceMustExist(request) |
||||
.MapT(DoDeletion) |
||||
.Bind(t => t.ToEitherAsync()); |
||||
|
||||
private Task<Unit> DoDeletion(LibraryPath libraryPath) => |
||||
_libraryRepository.DeleteLocalPath(libraryPath.Id).ToUnit(); |
||||
|
||||
private async Task<Validation<BaseError, LibraryPath>> MediaSourceMustExist(DeleteLocalLibraryPath request) => |
||||
(await _libraryRepository.GetPath(request.LocalLibraryPathId)) |
||||
.HeadOrNone() |
||||
.ToValidation<BaseError>( |
||||
$"Local library path {request.LocalLibraryPathId} does not exist."); |
||||
} |
||||
} |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.Libraries |
||||
{ |
||||
public record LocalLibraryPathViewModel(int Id, int LibraryId, string Path); |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.Libraries |
||||
{ |
||||
public record LocalLibraryViewModel(int Id, string Name, LibraryMediaKind MediaKind); |
||||
} |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.Libraries |
||||
{ |
||||
internal static class Mapper |
||||
{ |
||||
public static LocalLibraryViewModel ProjectToViewModel(LocalLibrary library) => |
||||
new(library.Id, library.Name, library.MediaKind); |
||||
|
||||
public static LocalLibraryPathViewModel ProjectToViewModel(LibraryPath libraryPath) => |
||||
new(libraryPath.Id, libraryPath.LibraryId, libraryPath.Path); |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public record CountMediaItemsByLibraryPath(int LibraryPathId) : IRequest<int>; |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public class CountMediaItemsByLibraryPathHandler : IRequestHandler<CountMediaItemsByLibraryPath, int> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public CountMediaItemsByLibraryPathHandler(ILibraryRepository libraryRepository) => |
||||
_libraryRepository = libraryRepository; |
||||
|
||||
public Task<int> Handle(CountMediaItemsByLibraryPath request, CancellationToken cancellationToken) => |
||||
_libraryRepository.CountMediaItemsByPath(request.LibraryPathId); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public record GetLocalLibraries : IRequest<List<LocalLibraryViewModel>>; |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
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.Libraries.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public class GetLocalLibrariesHandler : IRequestHandler<GetLocalLibraries, List<LocalLibraryViewModel>> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public GetLocalLibrariesHandler(ILibraryRepository libraryRepository) => _libraryRepository = libraryRepository; |
||||
|
||||
public Task<List<LocalLibraryViewModel>> |
||||
Handle(GetLocalLibraries request, CancellationToken cancellationToken) => |
||||
_libraryRepository.GetAllLocal().Map(list => list.Map(ProjectToViewModel).ToList()); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public record GetLocalLibraryById(int LibraryId) : IRequest<Option<LocalLibraryViewModel>>; |
||||
} |
@ -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.Libraries.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public class GetLocalLibraryByIdHandler : IRequestHandler<GetLocalLibraryById, Option<LocalLibraryViewModel>> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public GetLocalLibraryByIdHandler(ILibraryRepository libraryRepository) => |
||||
_libraryRepository = libraryRepository; |
||||
|
||||
public Task<Option<LocalLibraryViewModel>> Handle( |
||||
GetLocalLibraryById request, |
||||
CancellationToken cancellationToken) => |
||||
_libraryRepository.GetLocal(request.LibraryId).MapT(ProjectToViewModel); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public record GetLocalLibraryPaths(int LocalLibraryId) : IRequest<List<LocalLibraryPathViewModel>>; |
||||
} |
@ -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.Libraries.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Libraries.Queries |
||||
{ |
||||
public class GetLocalLibraryPathsHandler : IRequestHandler<GetLocalLibraryPaths, List<LocalLibraryPathViewModel>> |
||||
{ |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
|
||||
public GetLocalLibraryPathsHandler(ILibraryRepository libraryRepository) => |
||||
_libraryRepository = libraryRepository; |
||||
|
||||
public Task<List<LocalLibraryPathViewModel>> Handle( |
||||
GetLocalLibraryPaths request, |
||||
CancellationToken cancellationToken) => |
||||
_libraryRepository.GetLocalPaths(request.LocalLibraryId) |
||||
.Map(list => list.Map(ProjectToViewModel).ToList()); |
||||
} |
||||
} |
@ -1,60 +1,70 @@
@@ -1,60 +1,70 @@
|
||||
using System; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
internal static class Mapper |
||||
{ |
||||
internal static TelevisionShowCardViewModel ProjectToViewModel(TelevisionShow televisionShow) => |
||||
internal static TelevisionShowCardViewModel ProjectToViewModel(ShowMetadata showMetadata) => |
||||
new( |
||||
televisionShow.Id, |
||||
televisionShow.Metadata?.Title, |
||||
televisionShow.Metadata?.Year.ToString(), |
||||
televisionShow.Metadata?.SortTitle, |
||||
televisionShow.Poster); |
||||
showMetadata.ShowId, |
||||
showMetadata.Title, |
||||
showMetadata.Year?.ToString(), |
||||
showMetadata.SortTitle, |
||||
GetPoster(showMetadata)); |
||||
|
||||
internal static TelevisionSeasonCardViewModel ProjectToViewModel(TelevisionSeason televisionSeason) => |
||||
internal static TelevisionSeasonCardViewModel ProjectToViewModel(Season season) => |
||||
new( |
||||
televisionSeason.TelevisionShow.Metadata?.Title, |
||||
televisionSeason.Id, |
||||
televisionSeason.Number, |
||||
GetSeasonName(televisionSeason.Number), |
||||
season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty), |
||||
season.Id, |
||||
season.SeasonNumber, |
||||
GetSeasonName(season.SeasonNumber), |
||||
string.Empty, |
||||
GetSeasonName(televisionSeason.Number), |
||||
televisionSeason.Poster, |
||||
televisionSeason.Number == 0 ? "S" : televisionSeason.Number.ToString()); |
||||
GetSeasonName(season.SeasonNumber), |
||||
season.SeasonMetadata.HeadOrNone().Map(GetPoster).IfNone(string.Empty), |
||||
season.SeasonNumber == 0 ? "S" : season.SeasonNumber.ToString()); |
||||
|
||||
internal static TelevisionEpisodeCardViewModel ProjectToViewModel( |
||||
TelevisionEpisodeMediaItem televisionEpisode) => |
||||
EpisodeMetadata episodeMetadata) => |
||||
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) => |
||||
episodeMetadata.EpisodeId, |
||||
episodeMetadata.ReleaseDate ?? DateTime.MinValue, |
||||
episodeMetadata.Episode.Season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty), |
||||
episodeMetadata.Title, |
||||
$"Episode {episodeMetadata.Episode.EpisodeNumber}", |
||||
episodeMetadata.Episode.EpisodeNumber.ToString(), |
||||
GetThumbnail(episodeMetadata), |
||||
episodeMetadata.Episode.EpisodeNumber.ToString()); |
||||
|
||||
internal static MovieCardViewModel ProjectToViewModel(MovieMetadata movieMetadata) => |
||||
new( |
||||
movie.Id, |
||||
movie.Metadata?.Title, |
||||
movie.Metadata?.Year?.ToString(), |
||||
movie.Metadata?.SortTitle, |
||||
movie.Poster); |
||||
|
||||
internal static SimpleMediaCollectionCardResultsViewModel |
||||
ProjectToViewModel(SimpleMediaCollection collection) => |
||||
movieMetadata.MovieId, |
||||
movieMetadata.Title, |
||||
movieMetadata.Year?.ToString(), |
||||
movieMetadata.SortTitle, |
||||
GetPoster(movieMetadata)); |
||||
|
||||
internal static CollectionCardResultsViewModel |
||||
ProjectToViewModel(Collection collection) => |
||||
new( |
||||
collection.Name, |
||||
collection.Movies.Map(ProjectToViewModel).ToList(), |
||||
collection.TelevisionShows.Map(ProjectToViewModel).ToList(), |
||||
collection.TelevisionSeasons.Map(ProjectToViewModel).ToList(), |
||||
collection.TelevisionEpisodes.Map(ProjectToViewModel).ToList()); |
||||
collection.MediaItems.OfType<Movie>().Map(m => ProjectToViewModel(m.MovieMetadata.Head())).ToList(), |
||||
collection.MediaItems.OfType<Show>().Map(s => ProjectToViewModel(s.ShowMetadata.Head())).ToList(), |
||||
collection.MediaItems.OfType<Season>().Map(ProjectToViewModel).ToList(), |
||||
collection.MediaItems.OfType<Episode>().Map(e => ProjectToViewModel(e.EpisodeMetadata.Head())) |
||||
.ToList()); |
||||
|
||||
private static string GetSeasonName(int number) => |
||||
number == 0 ? "Specials" : $"Season {number}"; |
||||
|
||||
private static string GetPoster(Metadata metadata) => |
||||
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster)) |
||||
.Match(a => a.Path, string.Empty); |
||||
|
||||
private static string GetThumbnail(Metadata metadata) => |
||||
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail)) |
||||
.Match(a => a.Path, string.Empty); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public record GetCollectionCards(int Id) : IRequest<Either<BaseError, CollectionCardResultsViewModel>>; |
||||
} |
@ -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 GetCollectionCardsHandler : IRequestHandler<GetCollectionCards, |
||||
Either<BaseError, CollectionCardResultsViewModel>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _collectionRepository; |
||||
|
||||
public GetCollectionCardsHandler(IMediaCollectionRepository collectionRepository) => |
||||
_collectionRepository = collectionRepository; |
||||
|
||||
public async Task<Either<BaseError, CollectionCardResultsViewModel>> Handle( |
||||
GetCollectionCards request, |
||||
CancellationToken cancellationToken) => |
||||
(await _collectionRepository.GetCollectionWithItemsUntracked(request.Id)) |
||||
.ToEither(BaseError.New("Unable to load collection")) |
||||
.Map(ProjectToViewModel); |
||||
} |
||||
} |
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public record GetSimpleMediaCollectionCards |
||||
(int Id) : IRequest<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>; |
||||
} |
@ -1,26 +0,0 @@
@@ -1,26 +0,0 @@
|
||||
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 ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddEpisodeToCollection(int CollectionId, int EpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public class |
||||
AddEpisodeToCollectionHandler : MediatR.IRequestHandler<AddEpisodeToCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
private readonly ITelevisionRepository _televisionRepository; |
||||
|
||||
public AddEpisodeToCollectionHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
ITelevisionRepository televisionRepository) |
||||
{ |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
_televisionRepository = televisionRepository; |
||||
} |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
AddEpisodeToCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(_ => ApplyAddTelevisionEpisodeRequest(request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<Unit> ApplyAddTelevisionEpisodeRequest(AddEpisodeToCollection request) => |
||||
_mediaCollectionRepository.AddMediaItem(request.CollectionId, request.EpisodeId); |
||||
|
||||
private async Task<Validation<BaseError, Unit>> Validate(AddEpisodeToCollection request) => |
||||
(await CollectionMustExist(request), await ValidateEpisode(request)) |
||||
.Apply((_, _) => Unit.Default); |
||||
|
||||
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddEpisodeToCollection request) => |
||||
_mediaCollectionRepository.Get(request.CollectionId) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
|
||||
private Task<Validation<BaseError, Unit>> ValidateEpisode(AddEpisodeToCollection request) => |
||||
LoadTelevisionEpisode(request) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Episode does not exist")); |
||||
|
||||
private Task<Option<int>> LoadTelevisionEpisode(AddEpisodeToCollection request) => |
||||
_televisionRepository.GetEpisode(request.EpisodeId).MapT(e => e.Id); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddMovieToCollection(int CollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
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 AddMovieToCollectionHandler : MediatR.IRequestHandler<AddMovieToCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
private readonly IMovieRepository _movieRepository; |
||||
|
||||
public AddMovieToCollectionHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
IMovieRepository movieRepository) |
||||
{ |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
_movieRepository = movieRepository; |
||||
} |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
AddMovieToCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(_ => ApplyAddMoviesRequest(request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<Unit> ApplyAddMoviesRequest(AddMovieToCollection request) => |
||||
_mediaCollectionRepository.AddMediaItem(request.CollectionId, request.MovieId); |
||||
|
||||
private async Task<Validation<BaseError, Unit>> Validate(AddMovieToCollection request) => |
||||
(await CollectionMustExist(request), await ValidateMovies(request)) |
||||
.Apply((_, _) => Unit.Default); |
||||
|
||||
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddMovieToCollection request) => |
||||
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
|
||||
private Task<Validation<BaseError, Unit>> ValidateMovies(AddMovieToCollection request) => |
||||
LoadMovie(request) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Movie does not exist")); |
||||
|
||||
private Task<Option<Movie>> LoadMovie(AddMovieToCollection request) => |
||||
_movieRepository.GetMovie(request.MovieId); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddMovieToSimpleMediaCollection |
||||
(int MediaCollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
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,7 @@
@@ -0,0 +1,7 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddSeasonToCollection(int CollectionId, int SeasonId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
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 AddSeasonToCollectionHandler : MediatR.IRequestHandler<AddSeasonToCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
private readonly ITelevisionRepository _televisionRepository; |
||||
|
||||
public AddSeasonToCollectionHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
ITelevisionRepository televisionRepository) |
||||
{ |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
_televisionRepository = televisionRepository; |
||||
} |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
AddSeasonToCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(_ => ApplyAddTelevisionSeasonRequest(request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> ApplyAddTelevisionSeasonRequest(AddSeasonToCollection request) => |
||||
await _mediaCollectionRepository.AddMediaItem(request.CollectionId, request.SeasonId); |
||||
|
||||
private async Task<Validation<BaseError, Unit>> Validate(AddSeasonToCollection request) => |
||||
(await CollectionMustExist(request), await ValidateSeason(request)) |
||||
.Apply((_, _) => Unit.Default); |
||||
|
||||
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddSeasonToCollection request) => |
||||
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
|
||||
private Task<Validation<BaseError, Unit>> ValidateSeason(AddSeasonToCollection request) => |
||||
LoadTelevisionSeason(request) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Season does not exist")); |
||||
|
||||
private Task<Option<Season>> LoadTelevisionSeason( |
||||
AddSeasonToCollection request) => |
||||
_televisionRepository.GetSeason(request.SeasonId); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddShowToCollection(int CollectionId, int ShowId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
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 AddShowToCollectionHandler : MediatR.IRequestHandler<AddShowToCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
private readonly ITelevisionRepository _televisionRepository; |
||||
|
||||
public AddShowToCollectionHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
ITelevisionRepository televisionRepository) |
||||
{ |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
_televisionRepository = televisionRepository; |
||||
} |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
AddShowToCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(_ => ApplyAddTelevisionShowRequest(request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<Unit> ApplyAddTelevisionShowRequest(AddShowToCollection request) |
||||
=> _mediaCollectionRepository.AddMediaItem(request.CollectionId, request.ShowId); |
||||
|
||||
private async Task<Validation<BaseError, Unit>> Validate(AddShowToCollection request) => |
||||
(await CollectionMustExist(request), await ValidateShow(request)) |
||||
.Apply((_, _) => Unit.Default); |
||||
|
||||
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddShowToCollection request) => |
||||
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
|
||||
private Task<Validation<BaseError, Unit>> ValidateShow(AddShowToCollection request) => |
||||
LoadTelevisionShow(request) |
||||
.MapT(_ => Unit.Default) |
||||
.Map(v => v.ToValidation<BaseError>("Show does not exist")); |
||||
|
||||
private Task<Option<Show>> LoadTelevisionShow(AddShowToCollection request) => |
||||
_televisionRepository.GetShow(request.ShowId); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddTelevisionEpisodeToSimpleMediaCollection |
||||
(int MediaCollectionId, int TelevisionEpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -1,68 +0,0 @@
@@ -1,68 +0,0 @@
|
||||
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); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddTelevisionSeasonToSimpleMediaCollection |
||||
(int MediaCollectionId, int TelevisionSeasonId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -1,68 +0,0 @@
@@ -1,68 +0,0 @@
|
||||
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); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddTelevisionShowToSimpleMediaCollection |
||||
(int MediaCollectionId, int TelevisionShowId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -1,67 +0,0 @@
@@ -1,67 +0,0 @@
|
||||
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,8 @@
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record CreateCollection(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>; |
||||
} |
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record CreateSimpleMediaCollection |
||||
(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>; |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record RemoveItemsFromCollection(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>> |
||||
{ |
||||
public List<int> MediaItemIds { get; set; } = new(); |
||||
} |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
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 |
||||
RemoveItemsFromCollectionHandler : MediatR.IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public RemoveItemsFromCollectionHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
RemoveItemsFromCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<Unit> ApplyAddTelevisionEpisodeRequest( |
||||
RemoveItemsFromCollection request, |
||||
Collection collection) |
||||
{ |
||||
var itemsToRemove = collection.MediaItems |
||||
.Filter(m => request.MediaItemIds.Contains(m.Id)) |
||||
.ToList(); |
||||
|
||||
itemsToRemove.ForEach(m => collection.MediaItems.Remove(m)); |
||||
|
||||
return itemsToRemove.Any() |
||||
? _mediaCollectionRepository.Update(collection).ToUnit() |
||||
: Task.FromResult(Unit.Default); |
||||
} |
||||
|
||||
private Task<Validation<BaseError, Collection>> Validate( |
||||
RemoveItemsFromCollection request) => |
||||
CollectionMustExist(request); |
||||
|
||||
private Task<Validation<BaseError, Collection>> CollectionMustExist( |
||||
RemoveItemsFromCollection updateCollection) => |
||||
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.MediaCollectionId) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
} |
||||
} |
@ -1,15 +0,0 @@
@@ -1,15 +0,0 @@
|
||||
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(); |
||||
} |
||||
} |
@ -1,74 +0,0 @@
@@ -1,74 +0,0 @@
|
||||
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.")); |
||||
} |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record UpdateCollection |
||||
(int CollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
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 UpdateCollectionHandler : MediatR.IRequestHandler<UpdateCollection, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public UpdateCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
UpdateCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(c => ApplyUpdateRequest(c, request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> ApplyUpdateRequest(Collection c, UpdateCollection update) |
||||
{ |
||||
c.Name = update.Name; |
||||
await _mediaCollectionRepository.Update(c); |
||||
return Unit.Default; |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, Collection>> |
||||
Validate(UpdateCollection request) => |
||||
(await CollectionMustExist(request), ValidateName(request)) |
||||
.Apply((collectionToUpdate, _) => collectionToUpdate); |
||||
|
||||
private Task<Validation<BaseError, Collection>> CollectionMustExist( |
||||
UpdateCollection updateCollection) => |
||||
_mediaCollectionRepository.Get(updateCollection.CollectionId) |
||||
.Map(v => v.ToValidation<BaseError>("Collection does not exist.")); |
||||
|
||||
private Validation<BaseError, string> ValidateName(UpdateCollection updateSimpleMediaCollection) => |
||||
updateSimpleMediaCollection.NotEmpty(c => c.Name) |
||||
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name)); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record UpdateSimpleMediaCollection |
||||
(int MediaCollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -1,47 +0,0 @@
@@ -1,47 +0,0 @@
|
||||
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 |
||||
UpdateSimpleMediaCollectionHandler : MediatR.IRequestHandler<UpdateSimpleMediaCollection, |
||||
Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public UpdateSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public Task<Either<BaseError, Unit>> Handle( |
||||
UpdateSimpleMediaCollection request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(c => ApplyUpdateRequest(c, request)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> ApplyUpdateRequest(SimpleMediaCollection c, UpdateSimpleMediaCollection update) |
||||
{ |
||||
c.Name = update.Name; |
||||
await _mediaCollectionRepository.Update(c); |
||||
return Unit.Default; |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, SimpleMediaCollection>> |
||||
Validate(UpdateSimpleMediaCollection request) => |
||||
(await SimpleMediaCollectionMustExist(request), ValidateName(request)) |
||||
.Apply((simpleMediaCollectionToUpdate, _) => simpleMediaCollectionToUpdate); |
||||
|
||||
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist( |
||||
UpdateSimpleMediaCollection updateSimpleMediaCollection) => |
||||
_mediaCollectionRepository.GetSimpleMediaCollection(updateSimpleMediaCollection.MediaCollectionId) |
||||
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist.")); |
||||
|
||||
private Validation<BaseError, string> ValidateName(UpdateSimpleMediaCollection updateSimpleMediaCollection) => |
||||
updateSimpleMediaCollection.NotEmpty(c => c.Name) |
||||
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name)); |
||||
} |
||||
} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public record GetAllSimpleMediaCollections : IRequest<List<MediaCollectionViewModel>>; |
||||
} |
@ -1,25 +0,0 @@
@@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using MediatR; |
||||
using static ErsatzTV.Application.MediaCollections.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public class |
||||
GetAllSimpleMediaCollectionsHandler : IRequestHandler<GetAllSimpleMediaCollections, |
||||
List<MediaCollectionViewModel>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public GetAllSimpleMediaCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public async Task<List<MediaCollectionViewModel>> Handle( |
||||
GetAllSimpleMediaCollections request, |
||||
CancellationToken cancellationToken) => |
||||
(await _mediaCollectionRepository.GetSimpleMediaCollections()).Map(ProjectToViewModel).ToList(); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public record GetCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>; |
||||
} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public record GetSimpleMediaCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>; |
||||
} |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
namespace ErsatzTV.Application.MediaItems |
||||
{ |
||||
public record MediaItemViewModel(int Id, int MediaSourceId, string Path); |
||||
public record MediaItemViewModel(int Id, int LibraryPathId); |
||||
} |
||||
|
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Application.MediaItems |
||||
{ |
||||
public record NamedMediaItemViewModel(int MediaItemId, string Name); |
||||
} |
@ -1,10 +0,0 @@
@@ -1,10 +0,0 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public record CreateLocalMediaSource |
||||
(string Name, MediaType MediaType, string Folder) : IRequest<Either<BaseError, MediaSourceViewModel>>; |
||||
} |
@ -1,78 +0,0 @@
@@ -1,78 +0,0 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
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 MediatR; |
||||
using static ErsatzTV.Application.MediaSources.Mapper; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public class CreateLocalMediaSourceHandler : IRequestHandler<CreateLocalMediaSource, |
||||
Either<BaseError, MediaSourceViewModel>> |
||||
{ |
||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||
|
||||
public CreateLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) => |
||||
_mediaSourceRepository = mediaSourceRepository; |
||||
|
||||
public Task<Either<BaseError, MediaSourceViewModel>> Handle( |
||||
CreateLocalMediaSource request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request).MapT(PersistLocalMediaSource).Bind(v => v.ToEitherAsync()); |
||||
|
||||
private Task<MediaSourceViewModel> PersistLocalMediaSource(LocalMediaSource c) => |
||||
_mediaSourceRepository.Add(c).Map(ProjectToViewModel); |
||||
|
||||
private async Task<Validation<BaseError, LocalMediaSource>> Validate(CreateLocalMediaSource request) => |
||||
(await ValidateName(request), await ValidateFolder(request)) |
||||
.Apply( |
||||
(name, folder) => |
||||
new LocalMediaSource |
||||
{ |
||||
Name = name, |
||||
MediaType = request.MediaType, |
||||
Folder = folder |
||||
}); |
||||
|
||||
private async Task<Validation<BaseError, string>> ValidateName(CreateLocalMediaSource request) |
||||
{ |
||||
List<string> allNames = await _mediaSourceRepository.GetAll() |
||||
.Map(list => list.Map(c => c.Name).ToList()); |
||||
|
||||
Validation<BaseError, string> result1 = request.NotEmpty(c => c.Name) |
||||
.Bind(_ => request.NotLongerThan(50)(c => c.Name)); |
||||
|
||||
var result2 = Optional(request.Name) |
||||
.Filter(name => !allNames.Contains(name)) |
||||
.ToValidation<BaseError>("Media source name must be unique"); |
||||
|
||||
return (result1, result2).Apply((_, _) => request.Name); |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, string>> ValidateFolder(CreateLocalMediaSource request) |
||||
{ |
||||
List<string> allFolders = await _mediaSourceRepository.GetAll() |
||||
.Map(list => list.OfType<LocalMediaSource>().Map(c => c.Folder).ToList()); |
||||
|
||||
|
||||
return Optional(request.Folder) |
||||
.Filter(folder => allFolders.ForAll(f => !AreSubPaths(f, folder))) |
||||
.ToValidation<BaseError>("Folder must not belong to another media source"); |
||||
} |
||||
|
||||
private static bool AreSubPaths(string path1, string path2) |
||||
{ |
||||
string one = path1 + Path.DirectorySeparatorChar; |
||||
string two = path2 + Path.DirectorySeparatorChar; |
||||
return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) || |
||||
two.StartsWith(one, StringComparison.OrdinalIgnoreCase); |
||||
} |
||||
} |
||||
} |
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public record DeleteLocalMediaSource(int LocalMediaSourceId) : IRequest<Either<BaseError, Task>>; |
||||
} |
@ -1,38 +0,0 @@
@@ -1,38 +0,0 @@
|
||||
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 MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public class |
||||
DeleteLocalMediaSourceHandler : IRequestHandler<DeleteLocalMediaSource, Either<BaseError, Task>> |
||||
{ |
||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||
|
||||
public DeleteLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) => |
||||
_mediaSourceRepository = mediaSourceRepository; |
||||
|
||||
public async Task<Either<BaseError, Task>> Handle( |
||||
DeleteLocalMediaSource request, |
||||
CancellationToken cancellationToken) => |
||||
(await MediaSourceMustExist(request)) |
||||
.Map(DoDeletion) |
||||
.ToEither<Task>(); |
||||
|
||||
private async Task DoDeletion(LocalMediaSource mediaSource) => |
||||
await _mediaSourceRepository.Delete(mediaSource.Id); |
||||
|
||||
private async Task<Validation<BaseError, LocalMediaSource>> MediaSourceMustExist( |
||||
DeleteLocalMediaSource deleteMediaSource) => |
||||
(await _mediaSourceRepository.Get(deleteMediaSource.LocalMediaSourceId)) |
||||
.OfType<LocalMediaSource>() |
||||
.HeadOrNone() |
||||
.ToValidation<BaseError>( |
||||
$"Local media source {deleteMediaSource.LocalMediaSourceId} does not exist."); |
||||
} |
||||
} |
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Locking; |
||||
using ErsatzTV.Core.Interfaces.Metadata; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.Extensions.Logging; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Either<BaseError, string>>, |
||||
IRequestHandler<ScanLocalLibraryIfNeeded, Either<BaseError, string>> |
||||
{ |
||||
private readonly IConfigElementRepository _configElementRepository; |
||||
private readonly IEntityLocker _entityLocker; |
||||
private readonly ILibraryRepository _libraryRepository; |
||||
private readonly ILogger<ScanLocalLibraryHandler> _logger; |
||||
private readonly IMovieFolderScanner _movieFolderScanner; |
||||
private readonly ITelevisionFolderScanner _televisionFolderScanner; |
||||
|
||||
public ScanLocalLibraryHandler( |
||||
ILibraryRepository libraryRepository, |
||||
IConfigElementRepository configElementRepository, |
||||
IMovieFolderScanner movieFolderScanner, |
||||
ITelevisionFolderScanner televisionFolderScanner, |
||||
IEntityLocker entityLocker, |
||||
ILogger<ScanLocalLibraryHandler> logger) |
||||
{ |
||||
_libraryRepository = libraryRepository; |
||||
_configElementRepository = configElementRepository; |
||||
_movieFolderScanner = movieFolderScanner; |
||||
_televisionFolderScanner = televisionFolderScanner; |
||||
_entityLocker = entityLocker; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
ForceScanLocalLibrary request, |
||||
CancellationToken cancellationToken) => Handle(request); |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
ScanLocalLibraryIfNeeded request, |
||||
CancellationToken cancellationToken) => Handle(request); |
||||
|
||||
private Task<Either<BaseError, string>> |
||||
Handle(IScanLocalLibrary request) => |
||||
Validate(request) |
||||
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalLibrary.Name)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> PerformScan(RequestParameters parameters) |
||||
{ |
||||
(LocalLibrary localLibrary, string ffprobePath, bool forceScan) = parameters; |
||||
|
||||
var lastScan = new DateTimeOffset(localLibrary.LastScan ?? DateTime.MinValue, TimeSpan.Zero); |
||||
if (forceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) |
||||
{ |
||||
foreach (LibraryPath libraryPath in localLibrary.Paths) |
||||
{ |
||||
switch (localLibrary.MediaKind) |
||||
{ |
||||
case LibraryMediaKind.Movies: |
||||
await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath); |
||||
break; |
||||
case LibraryMediaKind.Shows: |
||||
await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
localLibrary.LastScan = DateTime.UtcNow; |
||||
await _libraryRepository.UpdateLastScan(localLibrary); |
||||
} |
||||
else |
||||
{ |
||||
_logger.LogDebug( |
||||
"Skipping unforced scan of library {Name}", |
||||
localLibrary.Name); |
||||
} |
||||
|
||||
_entityLocker.UnlockLibrary(localLibrary.Id); |
||||
return Unit.Default; |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalLibrary request) => |
||||
(await LocalLibraryMustExist(request), await ValidateFFprobePath()) |
||||
.Apply( |
||||
(library, ffprobePath) => new RequestParameters( |
||||
library, |
||||
ffprobePath, |
||||
request.ForceScan)); |
||||
|
||||
private Task<Validation<BaseError, LocalLibrary>> LocalLibraryMustExist( |
||||
IScanLocalLibrary request) => |
||||
_libraryRepository.Get(request.LibraryId) |
||||
.Map(maybeLibrary => maybeLibrary.Map(ms => ms as LocalLibrary)) |
||||
.Map(v => v.ToValidation<BaseError>($"Local library {request.LibraryId} does not exist.")); |
||||
|
||||
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(LocalLibrary LocalLibrary, string FFprobePath, bool ForceScan); |
||||
} |
||||
} |
@ -1,111 +0,0 @@
@@ -1,111 +0,0 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Locking; |
||||
using ErsatzTV.Core.Interfaces.Metadata; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.Extensions.Logging; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
{ |
||||
public class ScanLocalMediaSourceHandler : IRequestHandler<ForceScanLocalMediaSource, Either<BaseError, string>>, |
||||
IRequestHandler<ScanLocalMediaSourceIfNeeded, Either<BaseError, string>> |
||||
{ |
||||
private readonly IConfigElementRepository _configElementRepository; |
||||
private readonly IEntityLocker _entityLocker; |
||||
private readonly ILogger<ScanLocalMediaSourceHandler> _logger; |
||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||
private readonly IMovieFolderScanner _movieFolderScanner; |
||||
private readonly ITelevisionFolderScanner _televisionFolderScanner; |
||||
|
||||
public ScanLocalMediaSourceHandler( |
||||
IMediaSourceRepository mediaSourceRepository, |
||||
IConfigElementRepository configElementRepository, |
||||
IMovieFolderScanner movieFolderScanner, |
||||
ITelevisionFolderScanner televisionFolderScanner, |
||||
IEntityLocker entityLocker, |
||||
ILogger<ScanLocalMediaSourceHandler> logger) |
||||
{ |
||||
_mediaSourceRepository = mediaSourceRepository; |
||||
_configElementRepository = configElementRepository; |
||||
_movieFolderScanner = movieFolderScanner; |
||||
_televisionFolderScanner = televisionFolderScanner; |
||||
_entityLocker = entityLocker; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
ForceScanLocalMediaSource request, |
||||
CancellationToken cancellationToken) => |
||||
Handle((IScanLocalMediaSource) request, cancellationToken); |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
ScanLocalMediaSourceIfNeeded request, |
||||
CancellationToken cancellationToken) => |
||||
Handle((IScanLocalMediaSource) request, cancellationToken); |
||||
|
||||
private Task<Either<BaseError, string>> |
||||
Handle(IScanLocalMediaSource request, CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> PerformScan(RequestParameters parameters) |
||||
{ |
||||
DateTimeOffset lastScan = parameters.LocalMediaSource.LastScan ?? DateTime.MinValue; |
||||
if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) |
||||
{ |
||||
switch (parameters.LocalMediaSource.MediaType) |
||||
{ |
||||
case MediaType.Movie: |
||||
await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); |
||||
break; |
||||
case MediaType.TvShow: |
||||
await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); |
||||
break; |
||||
} |
||||
|
||||
parameters.LocalMediaSource.LastScan = DateTimeOffset.Now; |
||||
await _mediaSourceRepository.Update(parameters.LocalMediaSource); |
||||
} |
||||
else |
||||
{ |
||||
_logger.LogDebug( |
||||
"Skipping unforced scan of media source {Folder}", |
||||
parameters.LocalMediaSource.Folder); |
||||
} |
||||
|
||||
_entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id); |
||||
return Unit.Default; |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalMediaSource request) => |
||||
(await LocalMediaSourceMustExist(request), await ValidateFFprobePath()) |
||||
.Apply( |
||||
(localMediaSource, ffprobePath) => new RequestParameters( |
||||
localMediaSource, |
||||
ffprobePath, |
||||
request.ForceScan)); |
||||
|
||||
private Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist( |
||||
IScanLocalMediaSource request) => |
||||
_mediaSourceRepository.Get(request.MediaSourceId) |
||||
.Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource)) |
||||
.Map(v => v.ToValidation<BaseError>($"Local media source {request.MediaSourceId} does not exist.")); |
||||
|
||||
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(LocalMediaSource LocalMediaSource, string FFprobePath, bool ForceScan); |
||||
} |
||||
} |
@ -1,7 +1,4 @@
@@ -1,7 +1,4 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources |
||||
namespace ErsatzTV.Application.MediaSources |
||||
{ |
||||
public record LocalMediaSourceViewModel(int Id, string Name, string Folder) |
||||
: MediaSourceViewModel(Id, Name, MediaSourceType.Local); |
||||
public record LocalMediaSourceViewModel(int Id) : MediaSourceViewModel(Id, "Local"); |
||||
} |
||||
|
@ -1,6 +1,4 @@
@@ -1,6 +1,4 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources |
||||
namespace ErsatzTV.Application.MediaSources |
||||
{ |
||||
public record MediaSourceViewModel(int Id, string Name, MediaSourceType SourceType); |
||||
public record MediaSourceViewModel(int Id, string Name); |
||||
} |
||||
|
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources |
||||
{ |
||||
public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel( |
||||
Id, |
||||
Name, |
||||
MediaSourceType.Plex); |
||||
} |
@ -1,10 +1,20 @@
@@ -1,10 +1,20 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
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); |
||||
internal static MovieViewModel ProjectToViewModel(Movie movie) |
||||
{ |
||||
MovieMetadata metadata = Optional(movie.MovieMetadata).Flatten().Head(); |
||||
return new MovieViewModel( |
||||
metadata.Title, |
||||
metadata.Year?.ToString(), |
||||
metadata.Plot, |
||||
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster)) |
||||
.Match(a => a.Path, string.Empty)); |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Commands |
||||
namespace ErsatzTV.Application.Plex.Commands |
||||
{ |
||||
public record SynchronizePlexLibraries(int PlexMediaSourceId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands |
||||
{ |
||||
public interface ISynchronizePlexLibraryById : IRequest<Either<BaseError, string>>, IBackgroundServiceRequest |
||||
{ |
||||
int PlexMediaSourceId { get; } |
||||
int PlexLibraryId { get; } |
||||
bool ForceScan { get; } |
||||
} |
||||
|
||||
public record SynchronizePlexLibraryByIdIfNeeded |
||||
(int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById |
||||
{ |
||||
public bool ForceScan => false; |
||||
} |
||||
|
||||
public record ForceSynchronizePlexLibraryById |
||||
(int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById |
||||
{ |
||||
public bool ForceScan => true; |
||||
} |
||||
} |
@ -0,0 +1,144 @@
@@ -0,0 +1,144 @@
|
||||
using System; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Locking; |
||||
using ErsatzTV.Core.Interfaces.Plex; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using ErsatzTV.Core.Plex; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.Extensions.Logging; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands |
||||
{ |
||||
public class |
||||
SynchronizePlexLibraryByIdHandler : IRequestHandler<ForceSynchronizePlexLibraryById, Either<BaseError, string>>, |
||||
IRequestHandler<SynchronizePlexLibraryByIdIfNeeded, Either<BaseError, string>> |
||||
{ |
||||
private readonly IEntityLocker _entityLocker; |
||||
private readonly ILogger<SynchronizePlexLibraryByIdHandler> _logger; |
||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||
private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner; |
||||
private readonly IPlexSecretStore _plexSecretStore; |
||||
|
||||
public SynchronizePlexLibraryByIdHandler( |
||||
IMediaSourceRepository mediaSourceRepository, |
||||
IPlexSecretStore plexSecretStore, |
||||
IPlexMovieLibraryScanner plexMovieLibraryScanner, |
||||
IEntityLocker entityLocker, |
||||
ILogger<SynchronizePlexLibraryByIdHandler> logger) |
||||
{ |
||||
_mediaSourceRepository = mediaSourceRepository; |
||||
_plexSecretStore = plexSecretStore; |
||||
_plexMovieLibraryScanner = plexMovieLibraryScanner; |
||||
_entityLocker = entityLocker; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
ForceSynchronizePlexLibraryById request, |
||||
CancellationToken cancellationToken) => Handle(request); |
||||
|
||||
public Task<Either<BaseError, string>> Handle( |
||||
SynchronizePlexLibraryByIdIfNeeded request, |
||||
CancellationToken cancellationToken) => Handle(request); |
||||
|
||||
private Task<Either<BaseError, string>> |
||||
Handle(ISynchronizePlexLibraryById request) => |
||||
Validate(request) |
||||
.MapT(parameters => Synchronize(parameters).Map(_ => parameters.Library.Name)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<Unit> Synchronize(RequestParameters parameters) |
||||
{ |
||||
var lastScan = new DateTimeOffset(parameters.Library.LastScan ?? DateTime.MinValue, TimeSpan.Zero); |
||||
if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) |
||||
{ |
||||
switch (parameters.Library.MediaKind) |
||||
{ |
||||
case LibraryMediaKind.Movies: |
||||
await _plexMovieLibraryScanner.ScanLibrary( |
||||
parameters.ConnectionParameters.ActiveConnection, |
||||
parameters.ConnectionParameters.PlexServerAuthToken, |
||||
parameters.Library); |
||||
break; |
||||
case LibraryMediaKind.Shows: |
||||
// TODO: plex tv scanner
|
||||
// await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath);
|
||||
break; |
||||
} |
||||
|
||||
parameters.Library.LastScan = DateTime.UtcNow; |
||||
await _mediaSourceRepository.Update(parameters.Library); |
||||
} |
||||
else |
||||
{ |
||||
_logger.LogDebug( |
||||
"Skipping unforced scan of plex media library {Name}", |
||||
parameters.Library.Name); |
||||
} |
||||
|
||||
// _entityLocker.UnlockMediaSource(parameters.MediaSource.Id);
|
||||
return Unit.Default; |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>> Validate(ISynchronizePlexLibraryById request) => |
||||
(await ValidateConnection(request), await PlexLibraryMustExist(request)) |
||||
.Apply( |
||||
(connectionParameters, plexLibrary) => new RequestParameters( |
||||
connectionParameters, |
||||
plexLibrary, |
||||
request.ForceScan |
||||
)); |
||||
|
||||
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection( |
||||
ISynchronizePlexLibraryById request) => |
||||
PlexMediaSourceMustExist(request) |
||||
.BindT(MediaSourceMustHaveActiveConnection) |
||||
.BindT(MediaSourceMustHaveToken); |
||||
|
||||
private Task<Validation<BaseError, PlexMediaSource>> PlexMediaSourceMustExist( |
||||
ISynchronizePlexLibraryById request) => |
||||
_mediaSourceRepository.GetPlex(request.PlexMediaSourceId) |
||||
.Map(v => v.ToValidation<BaseError>($"Plex media source {request.PlexMediaSourceId} does not exist.")); |
||||
|
||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection( |
||||
PlexMediaSource plexMediaSource) |
||||
{ |
||||
Option<PlexConnection> maybeConnection = |
||||
plexMediaSource.Connections.SingleOrDefault(c => c.IsActive); |
||||
return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection)) |
||||
.ToValidation<BaseError>("Plex media source requires an active connection"); |
||||
} |
||||
|
||||
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveToken( |
||||
ConnectionParameters connectionParameters) |
||||
{ |
||||
Option<PlexServerAuthToken> maybeToken = await |
||||
_plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier); |
||||
return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token }) |
||||
.ToValidation<BaseError>("Plex media source requires a token"); |
||||
} |
||||
|
||||
private Task<Validation<BaseError, PlexLibrary>> PlexLibraryMustExist( |
||||
ISynchronizePlexLibraryById request) => |
||||
_mediaSourceRepository.GetPlexLibrary(request.PlexLibraryId) |
||||
.Map(v => v.ToValidation<BaseError>($"Plex library {request.PlexLibraryId} does not exist.")); |
||||
|
||||
private record RequestParameters( |
||||
ConnectionParameters ConnectionParameters, |
||||
PlexLibrary Library, |
||||
bool ForceScan); |
||||
|
||||
private record ConnectionParameters( |
||||
PlexMediaSource PlexMediaSource, |
||||
PlexConnection ActiveConnection) |
||||
{ |
||||
public PlexServerAuthToken PlexServerAuthToken { get; set; } |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands |
||||
{ |
||||
public record UpdatePlexLibraryPreferences |
||||
(List<PlexLibraryPreference> Preferences) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
|
||||
public record PlexLibraryPreference(int Id, bool ShouldSyncItems); |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands |
||||
{ |
||||
public class |
||||
UpdatePlexLibraryPreferencesHandler : MediatR.IRequestHandler<UpdatePlexLibraryPreferences, |
||||
Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||
|
||||
public UpdatePlexLibraryPreferencesHandler(IMediaSourceRepository mediaSourceRepository) => |
||||
_mediaSourceRepository = mediaSourceRepository; |
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle( |
||||
UpdatePlexLibraryPreferences request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
IEnumerable<int> toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id); |
||||
await _mediaSourceRepository.DisablePlexLibrarySync(toDisable); |
||||
|
||||
IEnumerable<int> toEnable = request.Preferences.Filter(p => p.ShouldSyncItems).Map(p => p.Id); |
||||
await _mediaSourceRepository.EnablePlexLibrarySync(toEnable); |
||||
|
||||
return Unit.Default; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Application.Plex |
||||
{ |
||||
internal static class Mapper |
||||
{ |
||||
internal static PlexMediaSourceViewModel ProjectToViewModel(PlexMediaSource plexMediaSource) => |
||||
new( |
||||
plexMediaSource.Id, |
||||
plexMediaSource.ServerName, |
||||
Optional(plexMediaSource.Connections.SingleOrDefault(c => c.IsActive)).Match(c => c.Uri, string.Empty)); |
||||
|
||||
internal static PlexLibraryViewModel ProjectToViewModel(PlexLibrary library) => |
||||
new(library.Id, library.Name, library.MediaKind, library.ShouldSyncItems); |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Application.Plex |
||||
{ |
||||
public record PlexLibraryViewModel(int Id, string Name, LibraryMediaKind MediaKind, bool ShouldSyncItems); |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Application.MediaSources; |
||||
|
||||
namespace ErsatzTV.Application.Plex |
||||
{ |
||||
public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel(Id, Name); |
||||
} |
@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaSources.Queries |
||||
namespace ErsatzTV.Application.Plex.Queries |
||||
{ |
||||
public record GetAllPlexMediaSources : IRequest<List<PlexMediaSourceViewModel>>; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue