From 4369d0494012c316ec44555cee2a0b6237a51501 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Fri, 20 Jan 2023 05:37:39 -0600 Subject: [PATCH] scanner improvements (#1122) * optimize periodic scanning * set scanner process priority * update dependencies --- CHANGELOG.md | 2 + .../Commands/CallEmbyLibraryScannerHandler.cs | 41 +++++++++++++++++-- .../CallJellyfinLibraryScannerHandler.cs | 41 +++++++++++++++++-- .../Commands/CallLibraryScannerHandler.cs | 32 ++++++++++++++- .../CallLocalLibraryScannerHandler.cs | 39 ++++++++++++++++-- .../Commands/CallPlexLibraryScannerHandler.cs | 41 +++++++++++++++++-- ErsatzTV.Core/Errors/ScanIsNotRequired.cs | 8 ++++ .../ErsatzTV.Infrastructure.csproj | 2 +- ErsatzTV.Scanner/Worker.cs | 34 ++++++++++++--- ErsatzTV.sln | 8 ++-- ErsatzTV/ErsatzTV.csproj | 2 +- ErsatzTV/Services/EmbyService.cs | 21 ++++++++-- ErsatzTV/Services/JellyfinService.cs | 23 ++++++++--- ErsatzTV/Services/PlexService.cs | 21 ++++++++-- ErsatzTV/Services/WorkerService.cs | 21 ++++++++-- 15 files changed, 290 insertions(+), 46 deletions(-) create mode 100644 ErsatzTV.Core/Errors/ScanIsNotRequired.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 24418c855..c2d7a322c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Always use software pipeline for error display - 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 ### Fixed diff --git a/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs b/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs index 3494537fb..d250f15aa 100644 --- a/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs @@ -1,19 +1,26 @@ using System.Threading.Channels; using ErsatzTV.Application.Libraries; using ErsatzTV.Core; +using ErsatzTV.Core.Errors; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Emby; -public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler, +public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler, IRequestHandler>, IRequestHandler> { public CallEmbyLibraryScannerHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, ChannelWriter channel, IMediator mediator, IRuntimeInfo runtimeInfo) - : base(channel, mediator, runtimeInfo) + : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo) { } @@ -29,10 +36,18 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler, ISynchronizeEmbyLibraryById request, CancellationToken cancellationToken) { - Validation validation = Validate(); + Validation validation = await Validate(request); return await validation.Match( scanner => PerformScan(scanner, request, cancellationToken), - error => Task.FromResult>(error.Join())); + error => + { + foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) + { + return Task.FromResult>(scanIsNotRequired); + } + + return Task.FromResult>(error.Join()); + }); } private async Task> PerformScan( @@ -52,4 +67,22 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler, return await base.PerformScan(scanner, arguments, cancellationToken); } + + protected override async Task 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); + } } diff --git a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs index e676d2593..da15655cd 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs @@ -1,19 +1,26 @@ using System.Threading.Channels; using ErsatzTV.Application.Libraries; using ErsatzTV.Core; +using ErsatzTV.Core.Errors; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Jellyfin; -public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler, +public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler, IRequestHandler>, IRequestHandler> { public CallJellyfinLibraryScannerHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, ChannelWriter channel, IMediator mediator, IRuntimeInfo runtimeInfo) - : base(channel, mediator, runtimeInfo) + : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo) { } @@ -29,10 +36,18 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler, ISynchronizeJellyfinLibraryById request, CancellationToken cancellationToken) { - Validation validation = Validate(); + Validation validation = await Validate(request); return await validation.Match( scanner => PerformScan(scanner, request, cancellationToken), - error => Task.FromResult>(error.Join())); + error => + { + foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) + { + return Task.FromResult>(scanIsNotRequired); + } + + return Task.FromResult>(error.Join()); + }); } private async Task> PerformScan( @@ -52,4 +67,22 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler, return await base.PerformScan(scanner, arguments, cancellationToken); } + + protected override async Task 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); + } } diff --git a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs index 52ab30554..d1ffb1f22 100644 --- a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs @@ -3,9 +3,14 @@ using System.Threading.Channels; using CliWrap; using ErsatzTV.Application.Search; using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Errors; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.MediaSources; using ErsatzTV.Core.Metadata; using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Serilog; using Serilog.Events; @@ -13,18 +18,24 @@ using Serilog.Formatting.Compact.Reader; namespace ErsatzTV.Application.Libraries; -public abstract class CallLibraryScannerHandler +public abstract class CallLibraryScannerHandler { + private readonly IDbContextFactory _dbContextFactory; + private readonly IConfigElementRepository _configElementRepository; private readonly ChannelWriter _channel; private readonly IMediator _mediator; private readonly IRuntimeInfo _runtimeInfo; private string _libraryName; protected CallLibraryScannerHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, ChannelWriter channel, IMediator mediator, IRuntimeInfo runtimeInfo) { + _dbContextFactory = dbContextFactory; + _configElementRepository = configElementRepository; _channel = channel; _mediator = mediator; _runtimeInfo = runtimeInfo; @@ -131,8 +142,25 @@ public abstract class CallLibraryScannerHandler } } - protected Validation Validate() + protected abstract Task GetLastScan(TvContext dbContext, TRequest request); + protected abstract bool ScanIsRequired(DateTimeOffset lastScan, int libraryRefreshInterval, TRequest request); + + protected async Task> Validate(TRequest request) { + int libraryRefreshInterval = await _configElementRepository + .GetValue(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) ? "ErsatzTV.Scanner.exe" : "ErsatzTV.Scanner"; diff --git a/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs b/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs index 30a4345e0..ed835db78 100644 --- a/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs +++ b/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs @@ -1,19 +1,25 @@ using System.Threading.Channels; using ErsatzTV.Application.Libraries; using ErsatzTV.Core; +using ErsatzTV.Core.Errors; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.MediaSources; -public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler, +public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler, IRequestHandler>, IRequestHandler> { public CallLocalLibraryScannerHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, ChannelWriter channel, IMediator mediator, IRuntimeInfo runtimeInfo) - : base(channel, mediator, runtimeInfo) + : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo) { } @@ -27,10 +33,18 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler, private async Task> Handle(IScanLocalLibrary request, CancellationToken cancellationToken) { - Validation validation = Validate(); + Validation validation = await Validate(request); return await validation.Match( scanner => PerformScan(scanner, request, cancellationToken), - error => Task.FromResult>(error.Join())); + error => + { + foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) + { + return Task.FromResult>(scanIsNotRequired); + } + + return Task.FromResult>(error.Join()); + }); } private async Task> PerformScan( @@ -50,4 +64,21 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler, return await base.PerformScan(scanner, arguments, cancellationToken); } + + protected override async Task 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); + } } diff --git a/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs b/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs index 127657ca5..9284737fa 100644 --- a/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs @@ -1,19 +1,26 @@ using System.Threading.Channels; using ErsatzTV.Application.Libraries; using ErsatzTV.Core; +using ErsatzTV.Core.Errors; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Plex; -public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler, +public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler, IRequestHandler>, IRequestHandler> { public CallPlexLibraryScannerHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, ChannelWriter channel, IMediator mediator, IRuntimeInfo runtimeInfo) - : base(channel, mediator, runtimeInfo) + : base(dbContextFactory, configElementRepository, channel, mediator, runtimeInfo) { } @@ -29,10 +36,18 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler, ISynchronizePlexLibraryById request, CancellationToken cancellationToken) { - Validation validation = Validate(); + Validation validation = await Validate(request); return await validation.Match( scanner => PerformScan(scanner, request, cancellationToken), - error => Task.FromResult>(error.Join())); + error => + { + foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) + { + return Task.FromResult>(scanIsNotRequired); + } + + return Task.FromResult>(error.Join()); + }); } private async Task> PerformScan( @@ -57,4 +72,22 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler, return await base.PerformScan(scanner, arguments, cancellationToken); } + + protected override async Task 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); + } } diff --git a/ErsatzTV.Core/Errors/ScanIsNotRequired.cs b/ErsatzTV.Core/Errors/ScanIsNotRequired.cs new file mode 100644 index 000000000..59ef5ccff --- /dev/null +++ b/ErsatzTV.Core/Errors/ScanIsNotRequired.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.Core.Errors; + +public class ScanIsNotRequired : BaseError +{ + public ScanIsNotRequired() : base("Scan is not required") + { + } +} \ No newline at end of file diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index 717851498..2efb8c78d 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -11,7 +11,7 @@ - + diff --git a/ErsatzTV.Scanner/Worker.cs b/ErsatzTV.Scanner/Worker.cs index a817c8bc9..13355a532 100644 --- a/ErsatzTV.Scanner/Worker.cs +++ b/ErsatzTV.Scanner/Worker.cs @@ -1,4 +1,5 @@ using System.CommandLine; +using System.Diagnostics; using ErsatzTV.Scanner.Application.Emby; using ErsatzTV.Scanner.Application.Jellyfin; using ErsatzTV.Scanner.Application.MediaSources; @@ -82,6 +83,8 @@ public class Worker : BackgroundService if (IsScanningEnabled()) { bool force = context.ParseResult.GetValueForOption(forceOption); + SetProcessPriority(force); + int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -98,6 +101,8 @@ public class Worker : BackgroundService if (IsScanningEnabled()) { bool force = context.ParseResult.GetValueForOption(forceOption); + SetProcessPriority(force); + bool deep = context.ParseResult.GetValueForOption(deepOption); int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); @@ -115,6 +120,8 @@ public class Worker : BackgroundService if (IsScanningEnabled()) { bool force = context.ParseResult.GetValueForOption(forceOption); + SetProcessPriority(force); + int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -131,6 +138,8 @@ public class Worker : BackgroundService if (IsScanningEnabled()) { bool force = context.ParseResult.GetValueForOption(forceOption); + SetProcessPriority(force); + int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -150,18 +159,33 @@ public class Worker : BackgroundService return rootCommand; } -#if !DEBUG_NO_SYNC private bool IsScanningEnabled() { +#if !DEBUG_NO_SYNC // don't want to flag the logger as unused (only used when sync is disabled) ILogger _ = _logger; return true; - } #else - private bool IsScanningEnabled() - { _logger.LogInformation("Scanning is disabled via DEBUG_NO_SYNC"); return false; - } #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"); + } + } } diff --git a/ErsatzTV.sln b/ErsatzTV.sln index c91088d83..a79a1b050 100644 --- a/ErsatzTV.sln +++ b/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}.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|Any CPU.ActiveCfg = Debug No Sync|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.ActiveCfg = Debug|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.Build.0 = Debug|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}.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|Any CPU.ActiveCfg = Debug No Sync|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.ActiveCfg = Debug|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.Build.0 = Debug|Any CPU {2EF80455-953D-4696-831D-E8CBCA82B0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index 11ce00cd6..b79479755 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -55,7 +55,7 @@ - + diff --git a/ErsatzTV/Services/EmbyService.cs b/ErsatzTV/Services/EmbyService.cs index 3f0286f72..7d0dc10a6 100644 --- a/ErsatzTV/Services/EmbyService.cs +++ b/ErsatzTV/Services/EmbyService.cs @@ -4,6 +4,7 @@ using ErsatzTV.Application; using ErsatzTV.Application.Emby; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Locking; using MediatR; @@ -135,10 +136,22 @@ public class EmbyService : BackgroundService Either result = await mediator.Send(request, cancellationToken); result.BiIter( name => _logger.LogDebug("Done synchronizing emby library {Name}", name), - error => _logger.LogWarning( - "Unable to synchronize emby library {LibraryId}: {Error}", - request.EmbyLibraryId, - error.Value)); + error => + { + if (error is ScanIsNotRequired) + { + _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)) { diff --git a/ErsatzTV/Services/JellyfinService.cs b/ErsatzTV/Services/JellyfinService.cs index 7189ac11a..988213b72 100644 --- a/ErsatzTV/Services/JellyfinService.cs +++ b/ErsatzTV/Services/JellyfinService.cs @@ -4,6 +4,7 @@ using ErsatzTV.Application; using ErsatzTV.Application.Jellyfin; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Locking; using MediatR; @@ -160,11 +161,23 @@ public class JellyfinService : BackgroundService Either result = await mediator.Send(request, cancellationToken); result.BiIter( name => _logger.LogDebug("Done synchronizing jellyfin library {Name}", name), - error => _logger.LogWarning( - "Unable to synchronize jellyfin library {LibraryId}: {Error}", - request.JellyfinLibraryId, - error.Value)); - + error => + { + if (error is ScanIsNotRequired) + { + _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)) { entityLocker.UnlockLibrary(request.JellyfinLibraryId); diff --git a/ErsatzTV/Services/PlexService.cs b/ErsatzTV/Services/PlexService.cs index e0cd12f59..6c596228c 100644 --- a/ErsatzTV/Services/PlexService.cs +++ b/ErsatzTV/Services/PlexService.cs @@ -4,6 +4,7 @@ using ErsatzTV.Application; using ErsatzTV.Application.Plex; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Locking; using MediatR; @@ -159,10 +160,22 @@ public class PlexService : BackgroundService Either result = await mediator.Send(request, cancellationToken); result.BiIter( name => _logger.LogDebug("Done synchronizing plex library {Name}", name), - error => _logger.LogWarning( - "Unable to synchronize plex library {LibraryId}: {Error}", - request.PlexLibraryId, - error.Value)); + error => + { + if (error is ScanIsNotRequired) + { + _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)) { diff --git a/ErsatzTV/Services/WorkerService.cs b/ErsatzTV/Services/WorkerService.cs index 213d5a0bd..f86ad97cc 100644 --- a/ErsatzTV/Services/WorkerService.cs +++ b/ErsatzTV/Services/WorkerService.cs @@ -8,6 +8,7 @@ using ErsatzTV.Application.Playouts; using ErsatzTV.Application.Search; using ErsatzTV.Application.Subtitles; using ErsatzTV.Core; +using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Locking; using MediatR; @@ -71,10 +72,22 @@ public class WorkerService : BackgroundService name => _logger.LogDebug( "Done scanning local library {Library}", name), - error => _logger.LogWarning( - "Unable to scan local library {LibraryId}: {Error}", - scanLocalLibrary.LibraryId, - error.Value)); + error => + { + if (error is ScanIsNotRequired) + { + _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)) {