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; @@ -4,6 +4,19 @@ using MediatR;
namespace ErsatzTV.Application.MediaSources.Commands
{
public record ScanLocalMediaSource(int MediaSourceId) : IRequest<Either<Seq<BaseError>, string>>,
IBackgroundServiceRequest;
public interface IScanLocalMediaSource : IRequest<Either<BaseError, string>>, 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; @@ -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<ScanLocalMediaSource, Either<Seq<BaseError>, string>>
public class ScanLocalMediaSourceHandler : IRequestHandler<ForceScanLocalMediaSource, Either<BaseError, string>>,
IRequestHandler<ScanLocalMediaSourceIfNeeded, Either<BaseError, string>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IEntityLocker _entityLocker;
private readonly ILogger<ScanLocalMediaSourceHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMovieFolderScanner _movieFolderScanner;
private readonly ITelevisionFolderScanner _televisionFolderScanner;
@ -28,60 +29,76 @@ namespace ErsatzTV.Application.MediaSources.Commands @@ -28,60 +29,76 @@ namespace ErsatzTV.Application.MediaSources.Commands
IConfigElementRepository configElementRepository,
IMovieFolderScanner movieFolderScanner,
ITelevisionFolderScanner televisionFolderScanner,
IEntityLocker entityLocker)
IEntityLocker entityLocker,
ILogger<ScanLocalMediaSourceHandler> logger)
{
_mediaSourceRepository = mediaSourceRepository;
_configElementRepository = configElementRepository;
_movieFolderScanner = movieFolderScanner;
_televisionFolderScanner = televisionFolderScanner;
_entityLocker = entityLocker;
_logger = logger;
}
public Task<Either<Seq<BaseError>, string>>
Handle(ScanLocalMediaSource request, CancellationToken cancellationToken) =>
public Task<Either<BaseError, string>> Handle(
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)
.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)
{
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<Validation<BaseError, RequestParameters>> Validate(ScanLocalMediaSource request) =>
(await ValidateLocalMediaSource(request), await ValidateFFprobePath())
.Apply((localMediaSource, ffprobePath) => new RequestParameters(localMediaSource, ffprobePath));
private Task<Validation<BaseError, LocalMediaSource>> ValidateLocalMediaSource(ScanLocalMediaSource request) =>
LocalMediaSourceMustExist(request).BindT(ValidateLastScan);
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalMediaSource request) =>
(await LocalMediaSourceMustExist(request), await ValidateFFprobePath())
.Apply(
(localMediaSource, ffprobePath) => new RequestParameters(
localMediaSource,
ffprobePath,
request.ForceScan));
private Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist(
ScanLocalMediaSource request) =>
IScanLocalMediaSource request) =>
_mediaSourceRepository.Get(request.MediaSourceId)
.Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource))
.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() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists)
@ -89,6 +106,6 @@ namespace ErsatzTV.Application.MediaSources.Commands @@ -89,6 +106,6 @@ namespace ErsatzTV.Application.MediaSources.Commands
ffprobePath =>
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 @@ @@ -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");
}
});

2
ErsatzTV/Services/SchedulerService.cs

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

24
ErsatzTV/Services/WorkerService.cs

@ -6,7 +6,6 @@ using ErsatzTV.Application; @@ -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 @@ -56,29 +55,18 @@ namespace ErsatzTV.Services
buildPlayout.PlayoutId,
error.Value));
break;
case ScanLocalMediaSource scanLocalMediaSource:
Either<Seq<BaseError>, string> scanResult = await mediator.Send(
case IScanLocalMediaSource scanLocalMediaSource:
Either<BaseError, string> 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;
}
}

6
ErsatzTV/Shared/LocalMediaSources.razor

@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
<MudTooltip Text="Scan Media Source">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Disabled="@Locker.IsMediaSourceLocked(context.Id)"
OnClick="@(_ => RefreshAllMetadata(context))">
OnClick="@(_ => ScanMediaSource(context))">
</MudIconButton>
</MudTooltip>
}
@ -85,11 +85,11 @@ @@ -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();
}
}

Loading…
Cancel
Save