Browse Source

force scan from ui

pull/28/head
Jason Dove 6 years ago
parent
commit
174859eb10
  1. 17
      ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs
  2. 81
      ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs
  3. 2
      ErsatzTV/Pages/LocalMediaSourceEditor.razor
  4. 2
      ErsatzTV/Services/SchedulerService.cs
  5. 24
      ErsatzTV/Services/WorkerService.cs
  6. 6
      ErsatzTV/Shared/LocalMediaSources.razor

17
ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs

@ -4,6 +4,19 @@ using MediatR;
namespace ErsatzTV.Application.MediaSources.Commands namespace ErsatzTV.Application.MediaSources.Commands
{ {
public record ScanLocalMediaSource(int MediaSourceId) : IRequest<Either<Seq<BaseError>, string>>, public interface IScanLocalMediaSource : IRequest<Either<BaseError, string>>, IBackgroundServiceRequest
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;
}
} }

81
ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs

@ -4,21 +4,22 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using static LanguageExt.Prelude; using Microsoft.Extensions.Logging;
using Unit = LanguageExt.Unit; using Unit = LanguageExt.Unit;
namespace ErsatzTV.Application.MediaSources.Commands namespace ErsatzTV.Application.MediaSources.Commands
{ {
public class ScanLocalMediaSourceHandler : IRequestHandler<ScanLocalMediaSource, Either<Seq<BaseError>, string>> public class ScanLocalMediaSourceHandler : IRequestHandler<ForceScanLocalMediaSource, Either<BaseError, string>>,
IRequestHandler<ScanLocalMediaSourceIfNeeded, Either<BaseError, string>>
{ {
private readonly IConfigElementRepository _configElementRepository; private readonly IConfigElementRepository _configElementRepository;
private readonly IEntityLocker _entityLocker; private readonly IEntityLocker _entityLocker;
private readonly ILogger<ScanLocalMediaSourceHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMovieFolderScanner _movieFolderScanner; private readonly IMovieFolderScanner _movieFolderScanner;
private readonly ITelevisionFolderScanner _televisionFolderScanner; private readonly ITelevisionFolderScanner _televisionFolderScanner;
@ -28,60 +29,76 @@ namespace ErsatzTV.Application.MediaSources.Commands
IConfigElementRepository configElementRepository, IConfigElementRepository configElementRepository,
IMovieFolderScanner movieFolderScanner, IMovieFolderScanner movieFolderScanner,
ITelevisionFolderScanner televisionFolderScanner, ITelevisionFolderScanner televisionFolderScanner,
IEntityLocker entityLocker) IEntityLocker entityLocker,
ILogger<ScanLocalMediaSourceHandler> logger)
{ {
_mediaSourceRepository = mediaSourceRepository; _mediaSourceRepository = mediaSourceRepository;
_configElementRepository = configElementRepository; _configElementRepository = configElementRepository;
_movieFolderScanner = movieFolderScanner; _movieFolderScanner = movieFolderScanner;
_televisionFolderScanner = televisionFolderScanner; _televisionFolderScanner = televisionFolderScanner;
_entityLocker = entityLocker; _entityLocker = entityLocker;
_logger = logger;
} }
public Task<Either<Seq<BaseError>, string>> public Task<Either<BaseError, string>> Handle(
Handle(ScanLocalMediaSource request, CancellationToken cancellationToken) => 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) Validate(request)
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder)) .MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder))
.Bind(v => v.ToEither().MapAsync<Seq<BaseError>, Task<string>, string>(identity)); .Bind(v => v.ToEitherAsync());
private async Task<Unit> PerformScan(RequestParameters parameters) private async Task<Unit> 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: _logger.LogDebug(
await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); "Skipping unforced scan of media source {Folder}",
break; parameters.LocalMediaSource.Folder);
case MediaType.TvShow:
await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath);
break;
} }
parameters.LocalMediaSource.LastScan = DateTimeOffset.Now;
await _mediaSourceRepository.Update(parameters.LocalMediaSource);
_entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id); _entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id);
return Unit.Default; return Unit.Default;
} }
private async Task<Validation<BaseError, RequestParameters>> Validate(ScanLocalMediaSource request) => private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalMediaSource request) =>
(await ValidateLocalMediaSource(request), await ValidateFFprobePath()) (await LocalMediaSourceMustExist(request), await ValidateFFprobePath())
.Apply((localMediaSource, ffprobePath) => new RequestParameters(localMediaSource, ffprobePath)); .Apply(
(localMediaSource, ffprobePath) => new RequestParameters(
private Task<Validation<BaseError, LocalMediaSource>> ValidateLocalMediaSource(ScanLocalMediaSource request) => localMediaSource,
LocalMediaSourceMustExist(request).BindT(ValidateLastScan); ffprobePath,
request.ForceScan));
private Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist( private Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist(
ScanLocalMediaSource request) => IScanLocalMediaSource request) =>
_mediaSourceRepository.Get(request.MediaSourceId) _mediaSourceRepository.Get(request.MediaSourceId)
.Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource)) .Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource))
.Map(v => v.ToValidation<BaseError>($"Local media source {request.MediaSourceId} does not exist.")); .Map(v => v.ToValidation<BaseError>($"Local media source {request.MediaSourceId} does not exist."));
private Task<Validation<BaseError, LocalMediaSource>> ValidateLastScan(
LocalMediaSource localMediaSource) =>
Optional(localMediaSource)
.Filter(lms => lms.LastScan is null || lms.LastScan < DateTimeOffset.Now - TimeSpan.FromHours(6))
.ToValidation<BaseError>(new MediaSourceRecentlyScanned(localMediaSource.Folder))
.AsTask();
private Task<Validation<BaseError, string>> ValidateFFprobePath() => private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath) _configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists) .FilterT(File.Exists)
@ -89,6 +106,6 @@ namespace ErsatzTV.Application.MediaSources.Commands
ffprobePath => ffprobePath =>
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system")); ffprobePath.ToValidation<BaseError>("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);
} }
} }

2
ErsatzTV/Pages/LocalMediaSourceEditor.razor

@ -87,7 +87,7 @@
{ {
if (Locker.LockMediaSource(vm.Id)) if (Locker.LockMediaSource(vm.Id))
{ {
await Channel.WriteAsync(new ScanLocalMediaSource(vm.Id)); await Channel.WriteAsync(new ForceScanLocalMediaSource(vm.Id));
NavigationManager.NavigateTo("/media/sources"); NavigationManager.NavigateTo("/media/sources");
} }
}); });

2
ErsatzTV/Services/SchedulerService.cs

@ -82,7 +82,7 @@ namespace ErsatzTV.Services
if (_entityLocker.LockMediaSource(mediaSourceId)) if (_entityLocker.LockMediaSource(mediaSourceId))
{ {
await _channel.WriteAsync( await _channel.WriteAsync(
new ScanLocalMediaSource(mediaSourceId), new ScanLocalMediaSourceIfNeeded(mediaSourceId),
cancellationToken); cancellationToken);
} }
} }

24
ErsatzTV/Services/WorkerService.cs

@ -6,7 +6,6 @@ using ErsatzTV.Application;
using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.MediaSources.Commands;
using ErsatzTV.Application.Playouts.Commands; using ErsatzTV.Application.Playouts.Commands;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -56,29 +55,18 @@ namespace ErsatzTV.Services
buildPlayout.PlayoutId, buildPlayout.PlayoutId,
error.Value)); error.Value));
break; break;
case ScanLocalMediaSource scanLocalMediaSource: case IScanLocalMediaSource scanLocalMediaSource:
Either<Seq<BaseError>, string> scanResult = await mediator.Send( Either<BaseError, string> scanResult = await mediator.Send(
scanLocalMediaSource, scanLocalMediaSource,
cancellationToken); cancellationToken);
scanResult.BiIter( scanResult.BiIter(
name => _logger.LogDebug( name => _logger.LogDebug(
"Done scanning local media source {MediaSource}", "Done scanning local media source {MediaSource}",
name), name),
errors => error => _logger.LogWarning(
{ "Unable to scan local media source {MediaSourceId}: {Error}",
BaseError error = errors.Head; scanLocalMediaSource.MediaSourceId,
if (error is MediaSourceRecentlyScanned recentlyScanned) error.Value));
{
_logger.LogInformation(recentlyScanned.Value);
}
else
{
_logger.LogWarning(
"Unable to scan local media source {MediaSourceId}: {Error}",
scanLocalMediaSource.MediaSourceId,
error.Value);
}
});
break; break;
} }
} }

6
ErsatzTV/Shared/LocalMediaSources.razor

@ -34,7 +34,7 @@
<MudTooltip Text="Scan Media Source"> <MudTooltip Text="Scan Media Source">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" <MudIconButton Icon="@Icons.Material.Filled.Refresh"
Disabled="@Locker.IsMediaSourceLocked(context.Id)" Disabled="@Locker.IsMediaSourceLocked(context.Id)"
OnClick="@(_ => RefreshAllMetadata(context))"> OnClick="@(_ => ScanMediaSource(context))">
</MudIconButton> </MudIconButton>
</MudTooltip> </MudTooltip>
} }
@ -85,11 +85,11 @@
} }
} }
private async Task RefreshAllMetadata(LocalMediaSourceViewModel mediaSource) private async Task ScanMediaSource(LocalMediaSourceViewModel mediaSource)
{ {
if (Locker.LockMediaSource(mediaSource.Id)) if (Locker.LockMediaSource(mediaSource.Id))
{ {
await Channel.WriteAsync(new ScanLocalMediaSource(mediaSource.Id)); await Channel.WriteAsync(new ForceScanLocalMediaSource(mediaSource.Id));
StateHasChanged(); StateHasChanged();
} }
} }

Loading…
Cancel
Save