using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.MediaCollections; public class AddItemsToPlaylistHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; private readonly IMovieRepository _movieRepository; private readonly ITelevisionRepository _televisionRepository; public AddItemsToPlaylistHandler( IDbContextFactory dbContextFactory, IMovieRepository movieRepository, ITelevisionRepository televisionRepository) { _dbContextFactory = dbContextFactory; _movieRepository = movieRepository; _televisionRepository = televisionRepository; } public async Task> Handle(AddItemsToPlaylist request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request, cancellationToken); return await validation.Apply(c => ApplyAddItemsRequest(dbContext, c, request, cancellationToken)); } private static async Task ApplyAddItemsRequest( TvContext dbContext, Playlist playlist, AddItemsToPlaylist request, CancellationToken cancellationToken) { var allItems = new Dictionary> { { CollectionType.Movie, request.MovieIds }, { CollectionType.TelevisionShow, request.ShowIds }, { CollectionType.TelevisionSeason, request.SeasonIds }, { CollectionType.Episode, request.EpisodeIds }, { CollectionType.Artist, request.ArtistIds }, { CollectionType.MusicVideo, request.MusicVideoIds }, { CollectionType.OtherVideo, request.OtherVideoIds }, { CollectionType.Song, request.SongIds }, { CollectionType.Image, request.ImageIds }, { CollectionType.RemoteStream, request.RemoteStreamIds } }; int index = playlist.Items.Count > 0 ? playlist.Items.Max(i => i.Index) + 1 : 0; foreach ((CollectionType collectionType, List ids) in allItems) { foreach (int id in ids) { var item = new PlaylistItem { Index = index++, CollectionType = collectionType, MediaItemId = id, PlaybackOrder = PlaybackOrder.Shuffle, IncludeInProgramGuide = true }; playlist.Items.Add(item); } } // Mutates the playlist's editor-visible item list, so bump the concurrency token (issue #253). playlist.Version++; await dbContext.SaveChangesForcingVersion(cancellationToken); return Unit.Default; } private async Task> Validate( TvContext dbContext, AddItemsToPlaylist request, CancellationToken cancellationToken) => (await PlaylistMustExist(dbContext, request, cancellationToken), await ValidateMovies(request), await ValidateShows(request), await ValidateSeasons(request), await ValidateEpisodes(request), await ValidateMediaItems(dbContext, request, cancellationToken)) .Apply((collection, _, _, _, _, _) => collection); private static async Task> PlaylistMustExist( TvContext dbContext, AddItemsToPlaylist request, CancellationToken cancellationToken) { Option maybePlaylist = await dbContext.Playlists .Include(c => c.Items) .SelectOneAsync(c => c.Id, c => c.Id == request.PlaylistId, cancellationToken); return maybePlaylist.ToValidation("Playlist does not exist.") .Bind(playlist => playlist.IsSystem ? BaseError.New("Cannot add items to system (generated) playlist") : Success(playlist)); } private Task> ValidateMovies(AddItemsToPlaylist request) => _movieRepository.AllMoviesExist(request.MovieIds) .Map(Optional) .Filter(v => v == true) .MapT(_ => Unit.Default) .Map(v => v.ToValidation("Movie does not exist")); private Task> ValidateShows(AddItemsToPlaylist request) => _televisionRepository.AllShowsExist(request.ShowIds) .Map(Optional) .Filter(v => v == true) .MapT(_ => Unit.Default) .Map(v => v.ToValidation("Show does not exist")); private Task> ValidateSeasons(AddItemsToPlaylist request) => _televisionRepository.AllSeasonsExist(request.SeasonIds) .Map(Optional) .Filter(v => v == true) .MapT(_ => Unit.Default) .Map(v => v.ToValidation("Season does not exist")); private Task> ValidateEpisodes(AddItemsToPlaylist request) => _televisionRepository.AllEpisodesExist(request.EpisodeIds) .Map(Optional) .Filter(v => v == true) .MapT(_ => Unit.Default) .Map(v => v.ToValidation("Episode does not exist")); private static async Task> ValidateMediaItems( TvContext dbContext, AddItemsToPlaylist request, CancellationToken cancellationToken) { List ids = GetRequestedMediaItemIds(request).Distinct().ToList(); int existingCount = await dbContext.MediaItems .CountAsync(mi => ids.Contains(mi.Id), cancellationToken); return existingCount == ids.Count ? Unit.Default : BaseError.New("Media item does not exist"); } private static IEnumerable GetRequestedMediaItemIds(AddItemsToPlaylist request) => request.MovieIds .Append(request.ShowIds) .Append(request.SeasonIds) .Append(request.EpisodeIds) .Append(request.ArtistIds) .Append(request.MusicVideoIds) .Append(request.OtherVideoIds) .Append(request.SongIds) .Append(request.ImageIds) .Append(request.RemoteStreamIds); }