From 174859eb1069dcd8f624f5b675aff4b79406e2a9 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Mon, 22 Feb 2021 12:41:20 -0600 Subject: [PATCH] force scan from ui --- .../Commands/ScanLocalMediaSource.cs | 17 +++- .../Commands/ScanLocalMediaSourceHandler.cs | 81 +++++++++++-------- ErsatzTV/Pages/LocalMediaSourceEditor.razor | 2 +- ErsatzTV/Services/SchedulerService.cs | 2 +- ErsatzTV/Services/WorkerService.cs | 24 ++---- ErsatzTV/Shared/LocalMediaSources.razor | 6 +- 6 files changed, 75 insertions(+), 57 deletions(-) diff --git a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs index 9f5a1259b..c2d46be01 100644 --- a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs +++ b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs @@ -4,6 +4,19 @@ using MediatR; namespace ErsatzTV.Application.MediaSources.Commands { - public record ScanLocalMediaSource(int MediaSourceId) : IRequest, string>>, - IBackgroundServiceRequest; + public interface IScanLocalMediaSource : IRequest>, IBackgroundServiceRequest + { + int MediaSourceId { get; } + bool ForceScan { get; } + } + + public record ScanLocalMediaSourceIfNeeded(int MediaSourceId) : IScanLocalMediaSource + { + public bool ForceScan => false; + } + + public record ForceScanLocalMediaSource(int MediaSourceId) : IScanLocalMediaSource + { + public bool ForceScan => true; + } } diff --git a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs index 74c96e649..4c387ee84 100644 --- a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs +++ b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs @@ -4,21 +4,22 @@ using System.Threading; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; -using static LanguageExt.Prelude; +using Microsoft.Extensions.Logging; using Unit = LanguageExt.Unit; namespace ErsatzTV.Application.MediaSources.Commands { - public class ScanLocalMediaSourceHandler : IRequestHandler, string>> + public class ScanLocalMediaSourceHandler : IRequestHandler>, + IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; private readonly IEntityLocker _entityLocker; + private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMovieFolderScanner _movieFolderScanner; private readonly ITelevisionFolderScanner _televisionFolderScanner; @@ -28,60 +29,76 @@ namespace ErsatzTV.Application.MediaSources.Commands IConfigElementRepository configElementRepository, IMovieFolderScanner movieFolderScanner, ITelevisionFolderScanner televisionFolderScanner, - IEntityLocker entityLocker) + IEntityLocker entityLocker, + ILogger logger) { _mediaSourceRepository = mediaSourceRepository; _configElementRepository = configElementRepository; _movieFolderScanner = movieFolderScanner; _televisionFolderScanner = televisionFolderScanner; _entityLocker = entityLocker; + _logger = logger; } - public Task, string>> - Handle(ScanLocalMediaSource request, CancellationToken cancellationToken) => + public Task> Handle( + ForceScanLocalMediaSource request, + CancellationToken cancellationToken) => + Handle((IScanLocalMediaSource) request, cancellationToken); + + public Task> Handle( + ScanLocalMediaSourceIfNeeded request, + CancellationToken cancellationToken) => + Handle((IScanLocalMediaSource) request, cancellationToken); + + private Task> + Handle(IScanLocalMediaSource request, CancellationToken cancellationToken) => Validate(request) .MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder)) - .Bind(v => v.ToEither().MapAsync, Task, string>(identity)); + .Bind(v => v.ToEitherAsync()); private async Task PerformScan(RequestParameters parameters) { - switch (parameters.LocalMediaSource.MediaType) + 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 { - case MediaType.Movie: - await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); - break; - case MediaType.TvShow: - await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); - break; + _logger.LogDebug( + "Skipping unforced scan of media source {Folder}", + parameters.LocalMediaSource.Folder); } - parameters.LocalMediaSource.LastScan = DateTimeOffset.Now; - await _mediaSourceRepository.Update(parameters.LocalMediaSource); _entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id); - return Unit.Default; } - private async Task> Validate(ScanLocalMediaSource request) => - (await ValidateLocalMediaSource(request), await ValidateFFprobePath()) - .Apply((localMediaSource, ffprobePath) => new RequestParameters(localMediaSource, ffprobePath)); - - private Task> ValidateLocalMediaSource(ScanLocalMediaSource request) => - LocalMediaSourceMustExist(request).BindT(ValidateLastScan); + private async Task> Validate(IScanLocalMediaSource request) => + (await LocalMediaSourceMustExist(request), await ValidateFFprobePath()) + .Apply( + (localMediaSource, ffprobePath) => new RequestParameters( + localMediaSource, + ffprobePath, + request.ForceScan)); private Task> LocalMediaSourceMustExist( - ScanLocalMediaSource request) => + IScanLocalMediaSource request) => _mediaSourceRepository.Get(request.MediaSourceId) .Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource)) .Map(v => v.ToValidation($"Local media source {request.MediaSourceId} does not exist.")); - private Task> ValidateLastScan( - LocalMediaSource localMediaSource) => - Optional(localMediaSource) - .Filter(lms => lms.LastScan is null || lms.LastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) - .ToValidation(new MediaSourceRecentlyScanned(localMediaSource.Folder)) - .AsTask(); - private Task> ValidateFFprobePath() => _configElementRepository.GetValue(ConfigElementKey.FFprobePath) .FilterT(File.Exists) @@ -89,6 +106,6 @@ namespace ErsatzTV.Application.MediaSources.Commands ffprobePath => ffprobePath.ToValidation("FFprobe path does not exist on the file system")); - private record RequestParameters(LocalMediaSource LocalMediaSource, string FFprobePath); + private record RequestParameters(LocalMediaSource LocalMediaSource, string FFprobePath, bool ForceScan); } } diff --git a/ErsatzTV/Pages/LocalMediaSourceEditor.razor b/ErsatzTV/Pages/LocalMediaSourceEditor.razor index e69f2d04b..f4edf64a7 100644 --- a/ErsatzTV/Pages/LocalMediaSourceEditor.razor +++ b/ErsatzTV/Pages/LocalMediaSourceEditor.razor @@ -87,7 +87,7 @@ { if (Locker.LockMediaSource(vm.Id)) { - await Channel.WriteAsync(new ScanLocalMediaSource(vm.Id)); + await Channel.WriteAsync(new ForceScanLocalMediaSource(vm.Id)); NavigationManager.NavigateTo("/media/sources"); } }); diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index 4501d0fff..6d74f23d4 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -82,7 +82,7 @@ namespace ErsatzTV.Services if (_entityLocker.LockMediaSource(mediaSourceId)) { await _channel.WriteAsync( - new ScanLocalMediaSource(mediaSourceId), + new ScanLocalMediaSourceIfNeeded(mediaSourceId), cancellationToken); } } diff --git a/ErsatzTV/Services/WorkerService.cs b/ErsatzTV/Services/WorkerService.cs index 5c918f2a3..809f41a52 100644 --- a/ErsatzTV/Services/WorkerService.cs +++ b/ErsatzTV/Services/WorkerService.cs @@ -6,7 +6,6 @@ using ErsatzTV.Application; using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.Playouts.Commands; using ErsatzTV.Core; -using ErsatzTV.Core.Errors; using LanguageExt; using MediatR; using Microsoft.Extensions.DependencyInjection; @@ -56,29 +55,18 @@ namespace ErsatzTV.Services buildPlayout.PlayoutId, error.Value)); break; - case ScanLocalMediaSource scanLocalMediaSource: - Either, string> scanResult = await mediator.Send( + case IScanLocalMediaSource scanLocalMediaSource: + Either scanResult = await mediator.Send( scanLocalMediaSource, cancellationToken); scanResult.BiIter( name => _logger.LogDebug( "Done scanning local media source {MediaSource}", name), - errors => - { - BaseError error = errors.Head; - if (error is MediaSourceRecentlyScanned recentlyScanned) - { - _logger.LogInformation(recentlyScanned.Value); - } - else - { - _logger.LogWarning( - "Unable to scan local media source {MediaSourceId}: {Error}", - scanLocalMediaSource.MediaSourceId, - error.Value); - } - }); + error => _logger.LogWarning( + "Unable to scan local media source {MediaSourceId}: {Error}", + scanLocalMediaSource.MediaSourceId, + error.Value)); break; } } diff --git a/ErsatzTV/Shared/LocalMediaSources.razor b/ErsatzTV/Shared/LocalMediaSources.razor index d34e114e1..bdd88d1d1 100644 --- a/ErsatzTV/Shared/LocalMediaSources.razor +++ b/ErsatzTV/Shared/LocalMediaSources.razor @@ -34,7 +34,7 @@ + OnClick="@(_ => ScanMediaSource(context))"> } @@ -85,11 +85,11 @@ } } - private async Task RefreshAllMetadata(LocalMediaSourceViewModel mediaSource) + private async Task ScanMediaSource(LocalMediaSourceViewModel mediaSource) { if (Locker.LockMediaSource(mediaSource.Id)) { - await Channel.WriteAsync(new ScanLocalMediaSource(mediaSource.Id)); + await Channel.WriteAsync(new ForceScanLocalMediaSource(mediaSource.Id)); StateHasChanged(); } }