using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading; using System.Threading.Tasks; using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using LanguageExt; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Unit = LanguageExt.Unit; namespace ErsatzTV.Application.Libraries.Commands { public class MoveLocalLibraryPathHandler : IRequestHandler> { private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; private readonly IDbContextFactory _dbContextFactory; private readonly IDbConnection _dbConnection; private readonly ILogger _logger; public MoveLocalLibraryPathHandler( ISearchIndex searchIndex, ISearchRepository searchRepository, IDbContextFactory dbContextFactory, IDbConnection dbConnection, ILogger logger) { _searchIndex = searchIndex; _searchRepository = searchRepository; _dbContextFactory = dbContextFactory; _dbConnection = dbConnection; _logger = logger; } public async Task> Handle( MoveLocalLibraryPath request, CancellationToken cancellationToken) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Validation validation = await Validate(dbContext, request); return await validation.Apply(parameters => MovePath(dbContext, parameters)); } private async Task MovePath(TvContext dbContext, Parameters parameters) { LibraryPath path = parameters.LibraryPath; LocalLibrary newLibrary = parameters.Library; path.LibraryId = newLibrary.Id; if (await dbContext.SaveChangesAsync() > 0) { List ids = await _dbConnection.QueryAsync( @"SELECT MediaItem.Id FROM MediaItem WHERE LibraryPathId = @LibraryPathId", new { LibraryPathId = path.Id }) .Map(result => result.ToList()); foreach (int id in ids) { Option maybeMediaItem = await _searchRepository.GetItemToIndex(id); foreach (MediaItem mediaItem in maybeMediaItem) { _logger.LogInformation("Moving item at {Path}", await GetPath(mediaItem)); await _searchIndex.UpdateItems(_searchRepository, new List { mediaItem }); } } } return Unit.Default; } private static async Task> Validate( TvContext dbContext, MoveLocalLibraryPath request) => (await LibraryPathMustExist(dbContext, request), await LocalLibraryMustExist(dbContext, request)) .Apply((libraryPath, localLibrary) => new Parameters(libraryPath, localLibrary)); private static Task> LibraryPathMustExist( TvContext dbContext, MoveLocalLibraryPath request) => dbContext.LibraryPaths .Include(lp => lp.Library) .SelectOneAsync(c => c.Id, c => c.Id == request.LibraryPathId) .Map(o => o.ToValidation("LibraryPath does not exist.")); private static Task> LocalLibraryMustExist( TvContext dbContext, MoveLocalLibraryPath request) => dbContext.LocalLibraries .Include(ll => ll.Paths) .SelectOneAsync(a => a.Id, a => a.Id == request.TargetLibraryId) .Map(o => o.ToValidation("LocalLibrary does not exist")); private async Task GetPath(MediaItem mediaItem) => mediaItem switch { Movie => await _dbConnection.QuerySingleAsync( @"SELECT Path FROM MediaFile INNER JOIN MediaVersion MV on MediaFile.MediaVersionId = MV.Id WHERE MV.MovieId = @Id", new { mediaItem.Id }), Episode => await _dbConnection.QuerySingleAsync( @"SELECT Path FROM MediaFile INNER JOIN MediaVersion MV on MediaFile.MediaVersionId = MV.Id WHERE MV.EpisodeId = @Id", new { mediaItem.Id }), MusicVideo => await _dbConnection.QuerySingleAsync( @"SELECT Path FROM MediaFile INNER JOIN MediaVersion MV on MediaFile.MediaVersionId = MV.Id WHERE MV.MusicVideoId = @Id", new { mediaItem.Id }), _ => null }; private record Parameters(LibraryPath LibraryPath, LocalLibrary Library); } }