Browse Source

scan media sources once every six hours

pull/28/head
Jason Dove 6 years ago
parent
commit
8f13989c52
  1. 2
      ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs
  2. 25
      ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs
  3. 5
      ErsatzTV.Core/Domain/MediaSource.cs
  4. 10
      ErsatzTV.Core/Errors/MediaSourceRecentlyScanned.cs
  5. 1
      ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs
  6. 14
      ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs
  7. 1308
      ErsatzTV.Infrastructure/Migrations/20210222120255_MediaSourceLastScan.Designer.cs
  8. 24
      ErsatzTV.Infrastructure/Migrations/20210222120255_MediaSourceLastScan.cs
  9. 3
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  10. 22
      ErsatzTV/Services/WorkerService.cs

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

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

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

@ -1,18 +1,21 @@ @@ -1,18 +1,21 @@
using System.IO;
using System;
using System.IO;
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 Unit = LanguageExt.Unit;
namespace ErsatzTV.Application.MediaSources.Commands
{
public class ScanLocalMediaSourceHandler : IRequestHandler<ScanLocalMediaSource, Either<BaseError, string>>
public class ScanLocalMediaSourceHandler : IRequestHandler<ScanLocalMediaSource, Either<Seq<BaseError>, string>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IEntityLocker _entityLocker;
@ -34,11 +37,11 @@ namespace ErsatzTV.Application.MediaSources.Commands @@ -34,11 +37,11 @@ namespace ErsatzTV.Application.MediaSources.Commands
_entityLocker = entityLocker;
}
public Task<Either<BaseError, string>>
public Task<Either<Seq<BaseError>, string>>
Handle(ScanLocalMediaSource request, CancellationToken cancellationToken) =>
Validate(request)
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder))
.Bind(v => v.ToEitherAsync());
.Bind(v => v.ToEither().MapAsync<Seq<BaseError>, Task<string>, string>(identity));
private async Task<Unit> PerformScan(RequestParameters parameters)
{
@ -52,21 +55,33 @@ namespace ErsatzTV.Application.MediaSources.Commands @@ -52,21 +55,33 @@ namespace ErsatzTV.Application.MediaSources.Commands
break;
}
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 LocalMediaSourceMustExist(request), await ValidateFFprobePath())
(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 Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist(
ScanLocalMediaSource 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)

5
ErsatzTV.Core/Domain/MediaSource.cs

@ -1,9 +1,12 @@ @@ -1,9 +1,12 @@
namespace ErsatzTV.Core.Domain
using System;
namespace ErsatzTV.Core.Domain
{
public abstract class MediaSource
{
public int Id { get; set; }
public MediaSourceType SourceType { get; set; }
public string Name { get; set; }
public DateTimeOffset? LastScan { get; set; }
}
}

10
ErsatzTV.Core/Errors/MediaSourceRecentlyScanned.cs

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
namespace ErsatzTV.Core.Errors
{
public class MediaSourceRecentlyScanned : BaseError
{
public MediaSourceRecentlyScanned(string folder) :
base($"Media source {folder} was already scanned recently; skipping scan.")
{
}
}
}

1
ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs

@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<Option<MediaSource>> Get(int id);
Task<Option<PlexMediaSource>> GetPlex(int id);
Task<int> CountMediaItems(int id);
Task Update(LocalMediaSource localMediaSource);
Task Update(PlexMediaSource plexMediaSource);
Task Delete(int id);
}

14
ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs

@ -60,11 +60,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -60,11 +60,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public Task<int> CountMediaItems(int id) =>
_dbContext.MediaItems.CountAsync(i => i.MediaSourceId == id);
public async Task Update(PlexMediaSource plexMediaSource)
{
_dbContext.PlexMediaSources.Update(plexMediaSource);
await _dbContext.SaveChangesAsync();
}
public Task Update(LocalMediaSource localMediaSource) =>
_dbContext.LocalMediaSources.Update(localMediaSource)
.AsTask()
.Bind(_ => _dbContext.SaveChangesAsync());
public Task Update(PlexMediaSource plexMediaSource) =>
_dbContext.PlexMediaSources.Update(plexMediaSource)
.AsTask()
.Bind(_ => _dbContext.SaveChangesAsync());
public async Task Delete(int id)
{

1308
ErsatzTV.Infrastructure/Migrations/20210222120255_MediaSourceLastScan.Designer.cs generated

File diff suppressed because it is too large Load Diff

24
ErsatzTV.Infrastructure/Migrations/20210222120255_MediaSourceLastScan.cs

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class MediaSourceLastScan : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTimeOffset>(
name: "LastScan",
table: "MediaSources",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LastScan",
table: "MediaSources");
}
}
}

3
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -248,6 +248,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -248,6 +248,9 @@ namespace ErsatzTV.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LastScan")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");

22
ErsatzTV/Services/WorkerService.cs

@ -6,6 +6,7 @@ using ErsatzTV.Application; @@ -6,6 +6,7 @@ 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,17 +57,28 @@ namespace ErsatzTV.Services @@ -56,17 +57,28 @@ namespace ErsatzTV.Services
error.Value));
break;
case ScanLocalMediaSource scanLocalMediaSource:
Either<BaseError, string> scanResult = await mediator.Send(
Either<Seq<BaseError>, string> scanResult = await mediator.Send(
scanLocalMediaSource,
cancellationToken);
scanResult.BiIter(
name => _logger.LogDebug(
"Done scanning local media source {MediaSource}",
name),
error => _logger.LogWarning(
"Unable to scan local media source {MediaSourceId}: {Error}",
scanLocalMediaSource.MediaSourceId,
error.Value));
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);
}
});
break;
}
}

Loading…
Cancel
Save