using System.IO; using System.Threading; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; using static LanguageExt.Prelude; using static ErsatzTV.Application.MediaItems.Mapper; namespace ErsatzTV.Application.MediaItems.Commands { public class CreateMediaItemHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly IMediaItemRepository _mediaItemRepository; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly ISmartCollectionBuilder _smartCollectionBuilder; public CreateMediaItemHandler( IMediaItemRepository mediaItemRepository, IMediaSourceRepository mediaSourceRepository, IConfigElementRepository configElementRepository, ISmartCollectionBuilder smartCollectionBuilder, ILocalMetadataProvider localMetadataProvider, ILocalStatisticsProvider localStatisticsProvider) { _mediaItemRepository = mediaItemRepository; _mediaSourceRepository = mediaSourceRepository; _configElementRepository = configElementRepository; _smartCollectionBuilder = smartCollectionBuilder; _localMetadataProvider = localMetadataProvider; _localStatisticsProvider = localStatisticsProvider; } public Task> Handle( CreateMediaItem request, CancellationToken cancellationToken) => Validate(request) .MapT(PersistMediaItem) .Bind(v => v.ToEitherAsync()); private async Task PersistMediaItem(RequestParameters parameters) { await _mediaItemRepository.Add(parameters.MediaItem); await _localStatisticsProvider.RefreshStatistics(parameters.FFprobePath, parameters.MediaItem); await _localMetadataProvider.RefreshMetadata(parameters.MediaItem); await _smartCollectionBuilder.RefreshSmartCollections(parameters.MediaItem); return ProjectToViewModel(parameters.MediaItem); } private async Task> Validate(CreateMediaItem request) => (await ValidateMediaSource(request), PathMustExist(request), await ValidateFFprobePath()) .Apply( (mediaSourceId, path, ffprobePath) => new RequestParameters( ffprobePath, new MediaItem { MediaSourceId = mediaSourceId, Path = path })); private async Task> ValidateMediaSource(CreateMediaItem createMediaItem) => (await MediaSourceMustExist(createMediaItem)).Bind(MediaSourceMustBeLocal); private async Task> MediaSourceMustExist(CreateMediaItem createMediaItem) => (await _mediaSourceRepository.Get(createMediaItem.MediaSourceId)) .ToValidation($"[MediaSource] {createMediaItem.MediaSourceId} does not exist."); private Validation MediaSourceMustBeLocal(MediaSource mediaSource) => Some(mediaSource) .Filter(ms => ms is LocalMediaSource) .ToValidation($"[MediaSource] {mediaSource.Id} must be a local media source") .Map(ms => ms.Id); private Validation PathMustExist(CreateMediaItem createMediaItem) => Some(createMediaItem.Path) .Filter(File.Exists) .ToValidation("[Path] does not exist on the file system"); private Task> ValidateFFprobePath() => _configElementRepository.GetValue(ConfigElementKey.FFprobePath) .FilterT(File.Exists) .Map( ffprobePath => ffprobePath.ToValidation("FFprobe path does not exist on the file system")); private record RequestParameters(string FFprobePath, MediaItem MediaItem); } }