Browse Source

scanner improvements (#1122)

* optimize periodic scanning

* set scanner process priority

* update dependencies
pull/1123/head
Jason Dove 4 years ago committed by GitHub
parent
commit
4369d04940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 41
      ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs
  3. 41
      ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs
  4. 32
      ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs
  5. 39
      ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs
  6. 41
      ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs
  7. 8
      ErsatzTV.Core/Errors/ScanIsNotRequired.cs
  8. 2
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  9. 34
      ErsatzTV.Scanner/Worker.cs
  10. 8
      ErsatzTV.sln
  11. 2
      ErsatzTV/ErsatzTV.csproj
  12. 21
      ErsatzTV/Services/EmbyService.cs
  13. 23
      ErsatzTV/Services/JellyfinService.cs
  14. 21
      ErsatzTV/Services/PlexService.cs
  15. 21
      ErsatzTV/Services/WorkerService.cs

2
CHANGELOG.md

@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- Always use software pipeline for error display - Always use software pipeline for error display
- This ensures errors will display even when hardware acceleration is misconfigured - This ensures errors will display even when hardware acceleration is misconfigured
- Call scanner process only when scanning is required based on library refresh interval
- Use idle priority for scanner process with unforced (automatic) library scans
## [0.7.2-beta] - 2023-01-05 ## [0.7.2-beta] - 2023-01-05
### Fixed ### Fixed

41
ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs

@ -1,19 +1,26 @@
using System.Threading.Channels; using System.Threading.Channels;
using ErsatzTV.Application.Libraries; using ErsatzTV.Application.Libraries;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Emby; namespace ErsatzTV.Application.Emby;
public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler, public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler<ISynchronizeEmbyLibraryById>,
IRequestHandler<ForceSynchronizeEmbyLibraryById, Either<BaseError, string>>, IRequestHandler<ForceSynchronizeEmbyLibraryById, Either<BaseError, string>>,
IRequestHandler<SynchronizeEmbyLibraryByIdIfNeeded, Either<BaseError, string>> IRequestHandler<SynchronizeEmbyLibraryByIdIfNeeded, Either<BaseError, string>>
{ {
public CallEmbyLibraryScannerHandler( public CallEmbyLibraryScannerHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel, ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator, IMediator mediator,
IRuntimeInfo runtimeInfo) IRuntimeInfo runtimeInfo)
: base(channel, mediator, runtimeInfo) : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo)
{ {
} }
@ -29,10 +36,18 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler,
ISynchronizeEmbyLibraryById request, ISynchronizeEmbyLibraryById request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Validation<BaseError, string> validation = Validate(); Validation<BaseError, string> validation = await Validate(request);
return await validation.Match( return await validation.Match(
scanner => PerformScan(scanner, request, cancellationToken), scanner => PerformScan(scanner, request, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join())); error =>
{
foreach (ScanIsNotRequired scanIsNotRequired in error.OfType<ScanIsNotRequired>())
{
return Task.FromResult<Either<BaseError, string>>(scanIsNotRequired);
}
return Task.FromResult<Either<BaseError, string>>(error.Join());
});
} }
private async Task<Either<BaseError, string>> PerformScan( private async Task<Either<BaseError, string>> PerformScan(
@ -52,4 +67,22 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler,
return await base.PerformScan(scanner, arguments, cancellationToken); return await base.PerformScan(scanner, arguments, cancellationToken);
} }
protected override async Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
ISynchronizeEmbyLibraryById request)
{
return await dbContext.EmbyLibraries
.SelectOneAsync(l => l.Id, l => l.Id == request.EmbyLibraryId)
.Match(l => l.LastScan ?? SystemTime.MinValueUtc, () => SystemTime.MaxValueUtc);
}
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
ISynchronizeEmbyLibraryById request)
{
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(libraryRefreshInterval);
return request.ForceScan || (libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now);
}
} }

41
ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs

@ -1,19 +1,26 @@
using System.Threading.Channels; using System.Threading.Channels;
using ErsatzTV.Application.Libraries; using ErsatzTV.Application.Libraries;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Jellyfin; namespace ErsatzTV.Application.Jellyfin;
public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler, public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler<ISynchronizeJellyfinLibraryById>,
IRequestHandler<ForceSynchronizeJellyfinLibraryById, Either<BaseError, string>>, IRequestHandler<ForceSynchronizeJellyfinLibraryById, Either<BaseError, string>>,
IRequestHandler<SynchronizeJellyfinLibraryByIdIfNeeded, Either<BaseError, string>> IRequestHandler<SynchronizeJellyfinLibraryByIdIfNeeded, Either<BaseError, string>>
{ {
public CallJellyfinLibraryScannerHandler( public CallJellyfinLibraryScannerHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel, ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator, IMediator mediator,
IRuntimeInfo runtimeInfo) IRuntimeInfo runtimeInfo)
: base(channel, mediator, runtimeInfo) : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo)
{ {
} }
@ -29,10 +36,18 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler,
ISynchronizeJellyfinLibraryById request, ISynchronizeJellyfinLibraryById request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Validation<BaseError, string> validation = Validate(); Validation<BaseError, string> validation = await Validate(request);
return await validation.Match( return await validation.Match(
scanner => PerformScan(scanner, request, cancellationToken), scanner => PerformScan(scanner, request, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join())); error =>
{
foreach (ScanIsNotRequired scanIsNotRequired in error.OfType<ScanIsNotRequired>())
{
return Task.FromResult<Either<BaseError, string>>(scanIsNotRequired);
}
return Task.FromResult<Either<BaseError, string>>(error.Join());
});
} }
private async Task<Either<BaseError, string>> PerformScan( private async Task<Either<BaseError, string>> PerformScan(
@ -52,4 +67,22 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler,
return await base.PerformScan(scanner, arguments, cancellationToken); return await base.PerformScan(scanner, arguments, cancellationToken);
} }
protected override async Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
ISynchronizeJellyfinLibraryById request)
{
return await dbContext.JellyfinLibraries
.SelectOneAsync(l => l.Id, l => l.Id == request.JellyfinLibraryId)
.Match(l => l.LastScan ?? SystemTime.MinValueUtc, () => SystemTime.MaxValueUtc);
}
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
ISynchronizeJellyfinLibraryById request)
{
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(libraryRefreshInterval);
return request.ForceScan || (libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now);
}
} }

32
ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs

@ -3,9 +3,14 @@ using System.Threading.Channels;
using CliWrap; using CliWrap;
using ErsatzTV.Application.Search; using ErsatzTV.Application.Search;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.MediaSources; using ErsatzTV.Core.MediaSources;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json; using Newtonsoft.Json;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
@ -13,18 +18,24 @@ using Serilog.Formatting.Compact.Reader;
namespace ErsatzTV.Application.Libraries; namespace ErsatzTV.Application.Libraries;
public abstract class CallLibraryScannerHandler public abstract class CallLibraryScannerHandler<TRequest>
{ {
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IConfigElementRepository _configElementRepository;
private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _channel; private readonly ChannelWriter<ISearchIndexBackgroundServiceRequest> _channel;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private string _libraryName; private string _libraryName;
protected CallLibraryScannerHandler( protected CallLibraryScannerHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel, ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator, IMediator mediator,
IRuntimeInfo runtimeInfo) IRuntimeInfo runtimeInfo)
{ {
_dbContextFactory = dbContextFactory;
_configElementRepository = configElementRepository;
_channel = channel; _channel = channel;
_mediator = mediator; _mediator = mediator;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
@ -131,8 +142,25 @@ public abstract class CallLibraryScannerHandler
} }
} }
protected Validation<BaseError, string> Validate() protected abstract Task<DateTimeOffset> GetLastScan(TvContext dbContext, TRequest request);
protected abstract bool ScanIsRequired(DateTimeOffset lastScan, int libraryRefreshInterval, TRequest request);
protected async Task<Validation<BaseError, string>> Validate(TRequest request)
{ {
int libraryRefreshInterval = await _configElementRepository
.GetValue<int>(ConfigElementKey.LibraryRefreshInterval)
.IfNoneAsync(0);
libraryRefreshInterval = Math.Clamp(libraryRefreshInterval, 0, 999_999);
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
DateTimeOffset lastScan = await GetLastScan(dbContext, request);
if (!ScanIsRequired(lastScan, libraryRefreshInterval, request))
{
return new ScanIsNotRequired();
}
string executable = _runtimeInfo.IsOSPlatform(OSPlatform.Windows) string executable = _runtimeInfo.IsOSPlatform(OSPlatform.Windows)
? "ErsatzTV.Scanner.exe" ? "ErsatzTV.Scanner.exe"
: "ErsatzTV.Scanner"; : "ErsatzTV.Scanner";

39
ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs

@ -1,19 +1,25 @@
using System.Threading.Channels; using System.Threading.Channels;
using ErsatzTV.Application.Libraries; using ErsatzTV.Application.Libraries;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.MediaSources; namespace ErsatzTV.Application.MediaSources;
public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler, public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler<IScanLocalLibrary>,
IRequestHandler<ForceScanLocalLibrary, Either<BaseError, string>>, IRequestHandler<ForceScanLocalLibrary, Either<BaseError, string>>,
IRequestHandler<ScanLocalLibraryIfNeeded, Either<BaseError, string>> IRequestHandler<ScanLocalLibraryIfNeeded, Either<BaseError, string>>
{ {
public CallLocalLibraryScannerHandler( public CallLocalLibraryScannerHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel, ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator, IMediator mediator,
IRuntimeInfo runtimeInfo) IRuntimeInfo runtimeInfo)
: base(channel, mediator, runtimeInfo) : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo)
{ {
} }
@ -27,10 +33,18 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler,
private async Task<Either<BaseError, string>> Handle(IScanLocalLibrary request, CancellationToken cancellationToken) private async Task<Either<BaseError, string>> Handle(IScanLocalLibrary request, CancellationToken cancellationToken)
{ {
Validation<BaseError, string> validation = Validate(); Validation<BaseError, string> validation = await Validate(request);
return await validation.Match( return await validation.Match(
scanner => PerformScan(scanner, request, cancellationToken), scanner => PerformScan(scanner, request, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join())); error =>
{
foreach (ScanIsNotRequired scanIsNotRequired in error.OfType<ScanIsNotRequired>())
{
return Task.FromResult<Either<BaseError, string>>(scanIsNotRequired);
}
return Task.FromResult<Either<BaseError, string>>(error.Join());
});
} }
private async Task<Either<BaseError, string>> PerformScan( private async Task<Either<BaseError, string>> PerformScan(
@ -50,4 +64,21 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler,
return await base.PerformScan(scanner, arguments, cancellationToken); return await base.PerformScan(scanner, arguments, cancellationToken);
} }
protected override async Task<DateTimeOffset> GetLastScan(TvContext dbContext, IScanLocalLibrary request)
{
return await dbContext.LibraryPaths
.Filter(lp => lp.LibraryId == request.LibraryId)
.ToListAsync()
.Map(list => list.Min(lp => lp.LastScan ?? SystemTime.MinValueUtc));
}
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
IScanLocalLibrary request)
{
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(libraryRefreshInterval);
return request.ForceScan || (libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now);
}
} }

41
ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs

@ -1,19 +1,26 @@
using System.Threading.Channels; using System.Threading.Channels;
using ErsatzTV.Application.Libraries; using ErsatzTV.Application.Libraries;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Plex; namespace ErsatzTV.Application.Plex;
public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler, public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler<ISynchronizePlexLibraryById>,
IRequestHandler<ForceSynchronizePlexLibraryById, Either<BaseError, string>>, IRequestHandler<ForceSynchronizePlexLibraryById, Either<BaseError, string>>,
IRequestHandler<SynchronizePlexLibraryByIdIfNeeded, Either<BaseError, string>> IRequestHandler<SynchronizePlexLibraryByIdIfNeeded, Either<BaseError, string>>
{ {
public CallPlexLibraryScannerHandler( public CallPlexLibraryScannerHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel, ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator, IMediator mediator,
IRuntimeInfo runtimeInfo) IRuntimeInfo runtimeInfo)
: base(channel, mediator, runtimeInfo) : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo)
{ {
} }
@ -29,10 +36,18 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler,
ISynchronizePlexLibraryById request, ISynchronizePlexLibraryById request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Validation<BaseError, string> validation = Validate(); Validation<BaseError, string> validation = await Validate(request);
return await validation.Match( return await validation.Match(
scanner => PerformScan(scanner, request, cancellationToken), scanner => PerformScan(scanner, request, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join())); error =>
{
foreach (ScanIsNotRequired scanIsNotRequired in error.OfType<ScanIsNotRequired>())
{
return Task.FromResult<Either<BaseError, string>>(scanIsNotRequired);
}
return Task.FromResult<Either<BaseError, string>>(error.Join());
});
} }
private async Task<Either<BaseError, string>> PerformScan( private async Task<Either<BaseError, string>> PerformScan(
@ -57,4 +72,22 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler,
return await base.PerformScan(scanner, arguments, cancellationToken); return await base.PerformScan(scanner, arguments, cancellationToken);
} }
protected override async Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
ISynchronizePlexLibraryById request)
{
return await dbContext.PlexLibraries
.SelectOneAsync(l => l.Id, l => l.Id == request.PlexLibraryId)
.Match(l => l.LastScan ?? SystemTime.MinValueUtc, () => SystemTime.MaxValueUtc);
}
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
ISynchronizePlexLibraryById request)
{
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(libraryRefreshInterval);
return request.ForceScan || (libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now);
}
} }

8
ErsatzTV.Core/Errors/ScanIsNotRequired.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Errors;
public class ScanIsNotRequired : BaseError
{
public ScanIsNotRequired() : base("Scan is not required")
{
}
}

2
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -11,7 +11,7 @@
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" /> <PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
<PackageReference Include="CliWrap" Version="3.6.0" /> <PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="Dapper" Version="2.0.123" /> <PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Jint" Version="3.0.0-beta-2045" /> <PackageReference Include="Jint" Version="3.0.0-beta-2046" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />

34
ErsatzTV.Scanner/Worker.cs

@ -1,4 +1,5 @@
using System.CommandLine; using System.CommandLine;
using System.Diagnostics;
using ErsatzTV.Scanner.Application.Emby; using ErsatzTV.Scanner.Application.Emby;
using ErsatzTV.Scanner.Application.Jellyfin; using ErsatzTV.Scanner.Application.Jellyfin;
using ErsatzTV.Scanner.Application.MediaSources; using ErsatzTV.Scanner.Application.MediaSources;
@ -82,6 +83,8 @@ public class Worker : BackgroundService
if (IsScanningEnabled()) if (IsScanningEnabled())
{ {
bool force = context.ParseResult.GetValueForOption(forceOption); bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
using IServiceScope scope = _serviceScopeFactory.CreateScope(); using IServiceScope scope = _serviceScopeFactory.CreateScope();
@ -98,6 +101,8 @@ public class Worker : BackgroundService
if (IsScanningEnabled()) if (IsScanningEnabled())
{ {
bool force = context.ParseResult.GetValueForOption(forceOption); bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
bool deep = context.ParseResult.GetValueForOption(deepOption); bool deep = context.ParseResult.GetValueForOption(deepOption);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
@ -115,6 +120,8 @@ public class Worker : BackgroundService
if (IsScanningEnabled()) if (IsScanningEnabled())
{ {
bool force = context.ParseResult.GetValueForOption(forceOption); bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
using IServiceScope scope = _serviceScopeFactory.CreateScope(); using IServiceScope scope = _serviceScopeFactory.CreateScope();
@ -131,6 +138,8 @@ public class Worker : BackgroundService
if (IsScanningEnabled()) if (IsScanningEnabled())
{ {
bool force = context.ParseResult.GetValueForOption(forceOption); bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
using IServiceScope scope = _serviceScopeFactory.CreateScope(); using IServiceScope scope = _serviceScopeFactory.CreateScope();
@ -150,18 +159,33 @@ public class Worker : BackgroundService
return rootCommand; return rootCommand;
} }
#if !DEBUG_NO_SYNC
private bool IsScanningEnabled() private bool IsScanningEnabled()
{ {
#if !DEBUG_NO_SYNC
// don't want to flag the logger as unused (only used when sync is disabled) // don't want to flag the logger as unused (only used when sync is disabled)
ILogger<Worker> _ = _logger; ILogger<Worker> _ = _logger;
return true; return true;
}
#else #else
private bool IsScanningEnabled()
{
_logger.LogInformation("Scanning is disabled via DEBUG_NO_SYNC"); _logger.LogInformation("Scanning is disabled via DEBUG_NO_SYNC");
return false; return false;
}
#endif #endif
}
private void SetProcessPriority(bool force)
{
if (force)
{
return;
}
try
{
using var process = Process.GetCurrentProcess();
process.PriorityClass = ProcessPriorityClass.Idle;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to set scanner priority");
}
}
} }

8
ErsatzTV.sln

@ -33,8 +33,8 @@ Global
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Release|Any CPU.Build.0 = Release|Any CPU {E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Release|Any CPU.Build.0 = Release|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.ActiveCfg = Debug No Sync|Any CPU {E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.ActiveCfg = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.Build.0 = Debug No Sync|Any CPU {E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.Build.0 = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.ActiveCfg = Debug No Sync|Any CPU {E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.Build.0 = Debug No Sync|Any CPU {E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU {C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -81,8 +81,8 @@ Global
{5664D574-2B8B-41C1-B091-8D3E887AE24E}.Release|Any CPU.Build.0 = Release|Any CPU {5664D574-2B8B-41C1-B091-8D3E887AE24E}.Release|Any CPU.Build.0 = Release|Any CPU
{5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug No Sync|Any CPU.ActiveCfg = Debug|Any CPU {5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug No Sync|Any CPU.ActiveCfg = Debug|Any CPU
{5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug No Sync|Any CPU.Build.0 = Debug|Any CPU {5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug No Sync|Any CPU.Build.0 = Debug|Any CPU
{5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug|Any CPU.ActiveCfg = Debug No Sync|Any CPU {5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug|Any CPU.Build.0 = Debug No Sync|Any CPU {5664D574-2B8B-41C1-B091-8D3E887AE24E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EF80455-953D-4696-831D-E8CBCA82B0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EF80455-953D-4696-831D-E8CBCA82B0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2EF80455-953D-4696-831D-E8CBCA82B0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EF80455-953D-4696-831D-E8CBCA82B0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EF80455-953D-4696-831D-E8CBCA82B0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EF80455-953D-4696-831D-E8CBCA82B0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU

2
ErsatzTV/ErsatzTV.csproj

@ -55,7 +55,7 @@
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" /> <PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
<PackageReference Include="FluentValidation" Version="11.4.0" /> <PackageReference Include="FluentValidation" Version="11.4.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
<PackageReference Include="HtmlSanitizer" Version="8.0.601" /> <PackageReference Include="HtmlSanitizer" Version="8.0.645" />
<PackageReference Include="LanguageExt.Core" Version="4.4.0" /> <PackageReference Include="LanguageExt.Core" Version="4.4.0" />
<PackageReference Include="Markdig" Version="0.30.4" /> <PackageReference Include="Markdig" Version="0.30.4" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" /> <PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />

21
ErsatzTV/Services/EmbyService.cs

@ -4,6 +4,7 @@ using ErsatzTV.Application;
using ErsatzTV.Application.Emby; using ErsatzTV.Application.Emby;
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 MediatR; using MediatR;
@ -135,10 +136,22 @@ public class EmbyService : BackgroundService
Either<BaseError, string> result = await mediator.Send(request, cancellationToken); Either<BaseError, string> result = await mediator.Send(request, cancellationToken);
result.BiIter( result.BiIter(
name => _logger.LogDebug("Done synchronizing emby library {Name}", name), name => _logger.LogDebug("Done synchronizing emby library {Name}", name),
error => _logger.LogWarning( error =>
"Unable to synchronize emby library {LibraryId}: {Error}", {
request.EmbyLibraryId, if (error is ScanIsNotRequired)
error.Value)); {
_logger.LogDebug(
"Scan is not required for emby library {LibraryId} at this time",
request.EmbyLibraryId);
}
else
{
_logger.LogWarning(
"Unable to synchronize emby library {LibraryId}: {Error}",
request.EmbyLibraryId,
error.Value);
}
});
if (entityLocker.IsLibraryLocked(request.EmbyLibraryId)) if (entityLocker.IsLibraryLocked(request.EmbyLibraryId))
{ {

23
ErsatzTV/Services/JellyfinService.cs

@ -4,6 +4,7 @@ using ErsatzTV.Application;
using ErsatzTV.Application.Jellyfin; using ErsatzTV.Application.Jellyfin;
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 MediatR; using MediatR;
@ -160,11 +161,23 @@ public class JellyfinService : BackgroundService
Either<BaseError, string> result = await mediator.Send(request, cancellationToken); Either<BaseError, string> result = await mediator.Send(request, cancellationToken);
result.BiIter( result.BiIter(
name => _logger.LogDebug("Done synchronizing jellyfin library {Name}", name), name => _logger.LogDebug("Done synchronizing jellyfin library {Name}", name),
error => _logger.LogWarning( error =>
"Unable to synchronize jellyfin library {LibraryId}: {Error}", {
request.JellyfinLibraryId, if (error is ScanIsNotRequired)
error.Value)); {
_logger.LogDebug(
"Scan is not required for jellyfin library {LibraryId} at this time",
request.JellyfinLibraryId);
}
else
{
_logger.LogWarning(
"Unable to synchronize jellyfin library {LibraryId}: {Error}",
request.JellyfinLibraryId,
error.Value);
}
});
if (entityLocker.IsLibraryLocked(request.JellyfinLibraryId)) if (entityLocker.IsLibraryLocked(request.JellyfinLibraryId))
{ {
entityLocker.UnlockLibrary(request.JellyfinLibraryId); entityLocker.UnlockLibrary(request.JellyfinLibraryId);

21
ErsatzTV/Services/PlexService.cs

@ -4,6 +4,7 @@ using ErsatzTV.Application;
using ErsatzTV.Application.Plex; using ErsatzTV.Application.Plex;
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 MediatR; using MediatR;
@ -159,10 +160,22 @@ public class PlexService : BackgroundService
Either<BaseError, string> result = await mediator.Send(request, cancellationToken); Either<BaseError, string> result = await mediator.Send(request, cancellationToken);
result.BiIter( result.BiIter(
name => _logger.LogDebug("Done synchronizing plex library {Name}", name), name => _logger.LogDebug("Done synchronizing plex library {Name}", name),
error => _logger.LogWarning( error =>
"Unable to synchronize plex library {LibraryId}: {Error}", {
request.PlexLibraryId, if (error is ScanIsNotRequired)
error.Value)); {
_logger.LogDebug(
"Scan is not required for plex library {LibraryId} at this time",
request.PlexLibraryId);
}
else
{
_logger.LogWarning(
"Unable to synchronize plex library {LibraryId}: {Error}",
request.PlexLibraryId,
error.Value);
}
});
if (entityLocker.IsLibraryLocked(request.PlexLibraryId)) if (entityLocker.IsLibraryLocked(request.PlexLibraryId))
{ {

21
ErsatzTV/Services/WorkerService.cs

@ -8,6 +8,7 @@ using ErsatzTV.Application.Playouts;
using ErsatzTV.Application.Search; using ErsatzTV.Application.Search;
using ErsatzTV.Application.Subtitles; using ErsatzTV.Application.Subtitles;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Locking;
using MediatR; using MediatR;
@ -71,10 +72,22 @@ public class WorkerService : BackgroundService
name => _logger.LogDebug( name => _logger.LogDebug(
"Done scanning local library {Library}", "Done scanning local library {Library}",
name), name),
error => _logger.LogWarning( error =>
"Unable to scan local library {LibraryId}: {Error}", {
scanLocalLibrary.LibraryId, if (error is ScanIsNotRequired)
error.Value)); {
_logger.LogDebug(
"Scan is not required for local library {LibraryId} at this time",
scanLocalLibrary.LibraryId);
}
else
{
_logger.LogWarning(
"Unable to scan local library {LibraryId}: {Error}",
scanLocalLibrary.LibraryId,
error.Value);
}
});
if (entityLocker.IsLibraryLocked(scanLocalLibrary.LibraryId)) if (entityLocker.IsLibraryLocked(scanLocalLibrary.LibraryId))
{ {

Loading…
Cancel
Save