diff --git a/CHANGELOG.md b/CHANGELOG.md index 83736603a..0a13809cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Poster will continue to be added as icon by default - Add buttons to edit Jellyfin and Emby connection information in **Media Sources** > **Jellyfin** and **Media Sources** > **Emby** - Add audio format `aac (latm)` for DVB-C compatibility; `aac` uses ADTS by default which is required in most cases +- Add deep scan option for external collections (Plex, Jellyfin, Emby) + - Jellyfin and Emby collection scans have always been deep scans + - Now, by default, they will be quick scans that trust Jellyfin and Emby's etags for detecting changes + - If a quick scan misses updating a collection, deep scans can be triggered manually ### Fixed - Fix NVIDIA startup errors on arm64 diff --git a/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs b/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs index 1671c0cfd..10fda6219 100644 --- a/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs @@ -91,6 +91,11 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler Unit.Default); } finally diff --git a/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs b/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs index 3e15183c8..b6511c5d1 100644 --- a/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs +++ b/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs @@ -2,5 +2,6 @@ using ErsatzTV.Core; namespace ErsatzTV.Application.Emby; -public record SynchronizeEmbyCollections(int EmbyMediaSourceId, bool ForceScan) : IRequest>, - IScannerBackgroundServiceRequest; +public record SynchronizeEmbyCollections(int EmbyMediaSourceId, bool ForceScan, bool DeepScan) + : IRequest>, + IScannerBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs index 50da2cfc4..6176aaa49 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs @@ -91,6 +91,11 @@ public class CallJellyfinCollectionScannerHandler : CallLibraryScannerHandler Unit.Default); } finally diff --git a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs index 2bb6a34a6..705bd8c03 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs @@ -2,6 +2,6 @@ using ErsatzTV.Core; namespace ErsatzTV.Application.Jellyfin; -public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : +public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan, bool DeepScan) : IRequest>, IScannerBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs b/ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs index ab0075c83..d79c45108 100644 --- a/ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs @@ -91,6 +91,11 @@ public class CallPlexCollectionScannerHandler : CallLibraryScannerHandler Unit.Default); } finally diff --git a/ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs index b64fd8898..b081770c9 100644 --- a/ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs @@ -2,5 +2,5 @@ using ErsatzTV.Core; namespace ErsatzTV.Application.Plex; -public record SynchronizePlexCollections(int PlexMediaSourceId, bool ForceScan) : IRequest>, - IScannerBackgroundServiceRequest; +public record SynchronizePlexCollections(int PlexMediaSourceId, bool ForceScan, bool DeepScan) + : IRequest>, IScannerBackgroundServiceRequest; diff --git a/ErsatzTV.Core/Domain/MediaItem/UknownEmbyMediaItem.cs b/ErsatzTV.Core/Domain/MediaItem/UknownEmbyMediaItem.cs new file mode 100644 index 000000000..587383046 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/UknownEmbyMediaItem.cs @@ -0,0 +1,7 @@ +namespace ErsatzTV.Core.Domain; + +public class UnknownEmbyMediaItem : MediaItem +{ + public string ItemType { get; set; } + public string ItemId { get; set; } +} diff --git a/ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs b/ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs index 12e8ac5ae..ec9dec142 100644 --- a/ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs +++ b/ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs @@ -2,7 +2,5 @@ public interface IEmbyCollectionScanner { - Task> ScanCollections( - string address, - string apiKey); + Task> ScanCollections(string address, string apiKey, bool deepScan); } diff --git a/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs b/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs index 950a59ef0..f1dade4e7 100644 --- a/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs +++ b/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs @@ -2,8 +2,5 @@ public interface IJellyfinCollectionScanner { - Task> ScanCollections( - string address, - string apiKey, - int mediaSourceId); + Task> ScanCollections(string address, string apiKey, int mediaSourceId, bool deepScan); } diff --git a/ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs b/ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs index 1637a3295..68b21ef01 100644 --- a/ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs +++ b/ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs @@ -8,5 +8,6 @@ public interface IPlexCollectionScanner Task> ScanCollections( PlexConnection connection, PlexServerAuthToken token, + bool deepScan, CancellationToken cancellationToken); } diff --git a/ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs index 4ddb2dcaf..6e74be61f 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs @@ -8,6 +8,6 @@ public interface IEmbyCollectionRepository Task AddCollection(EmbyCollection collection); Task RemoveCollection(EmbyCollection collection); Task> RemoveAllTags(EmbyCollection collection); - Task AddTag(MediaItem item, EmbyCollection collection); + Task> AddTag(MediaItem item, EmbyCollection collection); Task SetEtag(EmbyCollection collection); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs index 21f991082..ffeb61be0 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs @@ -47,36 +47,36 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository // movies result.AddRange( await dbContext.Connection.QueryAsync( - @"SELECT JM.Id FROM Tag T + @"SELECT EM.Id FROM Tag T INNER JOIN MovieMetadata MM on T.MovieMetadataId = MM.Id - INNER JOIN EmbyMovie JM on JM.Id = MM.MovieId + INNER JOIN EmbyMovie EM on EM.Id = MM.MovieId WHERE T.ExternalCollectionId = @ItemId", new { collection.ItemId })); // shows result.AddRange( await dbContext.Connection.QueryAsync( - @"SELECT JS.Id FROM Tag T + @"SELECT ES.Id FROM Tag T INNER JOIN ShowMetadata SM on T.ShowMetadataId = SM.Id - INNER JOIN EmbyShow JS on JS.Id = SM.ShowId + INNER JOIN EmbyShow ES on ES.Id = SM.ShowId WHERE T.ExternalCollectionId = @ItemId", new { collection.ItemId })); // seasons result.AddRange( await dbContext.Connection.QueryAsync( - @"SELECT JS.Id FROM Tag T + @"SELECT ES.Id FROM Tag T INNER JOIN SeasonMetadata SM on T.SeasonMetadataId = SM.Id - INNER JOIN EmbySeason JS on JS.Id = SM.SeasonId + INNER JOIN EmbySeason ES on ES.Id = SM.SeasonId WHERE T.ExternalCollectionId = @ItemId", new { collection.ItemId })); // episodes result.AddRange( await dbContext.Connection.QueryAsync( - @"SELECT JE.Id FROM Tag T + @"SELECT EE.Id FROM Tag T INNER JOIN EpisodeMetadata EM on T.EpisodeMetadataId = EM.Id - INNER JOIN EmbyEpisode JE on JE.Id = EM.EpisodeId + INNER JOIN EmbyEpisode EE on EE.Id = EM.EpisodeId WHERE T.ExternalCollectionId = @ItemId", new { collection.ItemId })); @@ -88,7 +88,7 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository return result; } - public async Task AddTag(MediaItem item, EmbyCollection collection) + public async Task> AddTag(MediaItem item, EmbyCollection collection) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); switch (item) @@ -97,6 +97,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository int movieId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT Id FROM EmbyMovie WHERE ItemId = @ItemId", new { movie.ItemId }); + if (movieId <= 0) + { + return Option.None; + } + await dbContext.Connection.ExecuteAsync( @"INSERT INTO Tag (Name, ExternalCollectionId, MovieMetadataId) SELECT @Name, @ItemId, Id FROM @@ -107,6 +112,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository int showId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT Id FROM EmbyShow WHERE ItemId = @ItemId", new { show.ItemId }); + if (showId <= 0) + { + return Option.None; + } + await dbContext.Connection.ExecuteAsync( @"INSERT INTO Tag (Name, ExternalCollectionId, ShowMetadataId) SELECT @Name, @ItemId, Id FROM @@ -117,6 +127,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository int seasonId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT Id FROM EmbySeason WHERE ItemId = @ItemId", new { season.ItemId }); + if (seasonId <= 0) + { + return Option.None; + } + await dbContext.Connection.ExecuteAsync( @"INSERT INTO Tag (Name, ExternalCollectionId, SeasonMetadataId) SELECT @Name, @ItemId, Id FROM @@ -127,6 +142,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository int episodeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT Id FROM EmbyEpisode WHERE ItemId = @ItemId", new { episode.ItemId }); + if (episodeId <= 0) + { + return Option.None; + } + await dbContext.Connection.ExecuteAsync( @"INSERT INTO Tag (Name, ExternalCollectionId, EpisodeMetadataId) SELECT @Name, @ItemId, Id FROM @@ -134,7 +154,7 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository new { collection.Name, collection.ItemId, EpisodeId = episodeId }); return episodeId; default: - return 0; + return Option.None; } } diff --git a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs index 646623092..b5934755d 100644 --- a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs +++ b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs @@ -137,6 +137,8 @@ public class EmbyApiClient : IEmbyApiClient if (_memoryCache.TryGetValue("emby_collections_library_item_id", out string itemId)) { + _logger.LogDebug("Emby collections library item id is {ItemId}", itemId); + return GetPagedLibraryContents( address, None, @@ -324,7 +326,7 @@ public class EmbyApiClient : IEmbyApiClient "Series" => new EmbyShow { ItemId = item.Id }, "Season" => new EmbySeason { ItemId = item.Id }, "Episode" => new EmbyEpisode { ItemId = item.Id }, - _ => None + _ => new UnknownEmbyMediaItem { ItemId = item.Id, ItemType = item.Type } }; } catch (Exception ex) diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs index dd2767690..31d5c9d11 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs @@ -2,5 +2,5 @@ namespace ErsatzTV.Scanner.Application.Emby; -public record SynchronizeEmbyCollections(string BaseUrl, int EmbyMediaSourceId, bool ForceScan) +public record SynchronizeEmbyCollections(string BaseUrl, int EmbyMediaSourceId, bool ForceScan, bool DeepScan) : IRequest>; diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs index 07827b36d..811f13138 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs @@ -52,6 +52,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler result = await _scanner.ScanCollections( parameters.ConnectionParameters.ActiveConnection.Address, - parameters.ConnectionParameters.ApiKey); + parameters.ConnectionParameters.ApiKey, + parameters.DeepScan); if (result.IsRight) { @@ -115,6 +117,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler>; diff --git a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs index 90d233664..2a3bf3a90 100644 --- a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs @@ -54,6 +54,7 @@ public class connectionParameters, connectionParameters.MediaSource, request.ForceScan, + request.DeepScan, libraryRefreshInterval, request.BaseUrl)); } @@ -99,7 +100,8 @@ public class Either result = await _scanner.ScanCollections( parameters.ConnectionParameters.ActiveConnection.Address, parameters.ConnectionParameters.ApiKey, - parameters.MediaSource.Id); + parameters.MediaSource.Id, + parameters.DeepScan); if (result.IsRight) { @@ -117,6 +119,7 @@ public class ConnectionParameters ConnectionParameters, JellyfinMediaSource MediaSource, bool ForceScan, + bool DeepScan, int LibraryRefreshInterval, string BaseUrl); diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs index 9f3686e68..50123324e 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs @@ -2,5 +2,5 @@ namespace ErsatzTV.Scanner.Application.Plex; -public record SynchronizePlexCollections(string BaseUrl, int PlexMediaSourceId, bool ForceScan) +public record SynchronizePlexCollections(string BaseUrl, int PlexMediaSourceId, bool ForceScan, bool DeepScan) : IRequest>; diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs index 1ed276f11..174518ae2 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs @@ -52,6 +52,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler result = await _scanner.ScanCollections( parameters.ConnectionParameters.ActiveConnection, parameters.ConnectionParameters.PlexServerAuthToken, + parameters.DeepScan, cancellationToken); if (result.IsRight) @@ -117,6 +119,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler> ScanCollections(string address, string apiKey) + public async Task> ScanCollections(string address, string apiKey, bool deepScan) { try { @@ -46,13 +46,13 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner Option maybeExisting = existingCollections.Find(c => c.ItemId == collection.ItemId); - // // skip if unchanged (etag) - // if (await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == - // collection.Etag) - // { - // _logger.LogDebug("Emby collection {Name} is unchanged", collection.Name); - // continue; - // } + // skip if unchanged (etag) + if (!deepScan && await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == + collection.Etag) + { + _logger.LogDebug("Emby collection {Name} is unchanged", collection.Name); + continue; + } // add if new if (maybeExisting.IsNone) @@ -98,16 +98,74 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner List removedIds = await _embyCollectionRepository.RemoveAllTags(collection); + var movies = 0; + var shows = 0; + var seasons = 0; + var episodes = 0; + var otherTypes = new Dictionary(); + // sync tags on items var addedIds = new List(); await foreach ((MediaItem item, int _) in items) { - addedIds.Add(await _embyCollectionRepository.AddTag(item, collection)); + Option maybeId = await _embyCollectionRepository.AddTag(item, collection); + foreach (int id in maybeId) + { + addedIds.Add(id); + + switch (item) + { + case Movie: + movies++; + break; + case Show: + shows++; + break; + case Season: + seasons++; + break; + case Episode: + episodes++; + break; + } + } + + if (item is UnknownEmbyMediaItem unk) + { + if (!otherTypes.TryAdd(unk.ItemType, 1)) + { + otherTypes[unk.ItemType]++; + } + } + } + + if (addedIds.Count > 0) + { + _logger.LogDebug( + "Emby collection {Name} contains {Count} items ({Movies} movies, {Shows} shows, {Seasons} seasons, {Episodes} episodes)", + collection.Name, + addedIds.Count, + movies, + shows, + seasons, + episodes); + } + else + { + _logger.LogDebug("Emby collection {Name} contains no items that are also in ErsatzTV", collection.Name); } - _logger.LogDebug("Emby collection {Name} contains {Count} items", collection.Name, addedIds.Count); + if (otherTypes.Count > 0) + { + _logger.LogDebug("Emby returned unsupported collection items {Items}", otherTypes); + } int[] changedIds = removedIds.Concat(addedIds).Distinct().ToArray(); + // if (changedIds.Length > 0) + // { + // _logger.LogDebug("Reindexing ids {Ids}", changedIds.ToList()); + // } + if (!await _scannerProxy.ReindexMediaItems(changedIds, CancellationToken.None)) { _logger.LogWarning("Failed to reindex media items from scanner process"); diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs index 39076f429..df01c0dae 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs @@ -26,7 +26,11 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner _logger = logger; } - public async Task> ScanCollections(string address, string apiKey, int mediaSourceId) + public async Task> ScanCollections( + string address, + string apiKey, + int mediaSourceId, + bool deepScan) { try { @@ -48,12 +52,13 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner Option maybeExisting = existingCollections.Find(c => c.ItemId == collection.ItemId); - // // skip if unchanged (etag) - // if (await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == collection.Etag) - // { - // _logger.LogDebug("Jellyfin collection {Name} is unchanged", collection.Name); - // continue; - // } + // skip if unchanged (etag) + if (!deepScan && await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == + collection.Etag) + { + _logger.LogDebug("Jellyfin collection {Name} is unchanged", collection.Name); + continue; + } // add if new if (maybeExisting.IsNone) diff --git a/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs index 79d36785b..3f9fbc9a7 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs @@ -30,6 +30,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner public async Task> ScanCollections( PlexConnection connection, PlexServerAuthToken token, + bool deepScan, CancellationToken cancellationToken) { try @@ -49,7 +50,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner Option maybeExisting = existingCollections.Find(c => c.Key == collection.Key); // skip if unchanged (etag) - if (await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == + if (!deepScan && await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) == collection.Etag) { _logger.LogDebug("Plex collection {Name} is unchanged", collection.Name); diff --git a/ErsatzTV.Scanner/Worker.cs b/ErsatzTV.Scanner/Worker.cs index 328d3f4f5..123b385dd 100644 --- a/ErsatzTV.Scanner/Worker.cs +++ b/ErsatzTV.Scanner/Worker.cs @@ -85,6 +85,7 @@ public class Worker : BackgroundService scanPlexCollectionsCommand.Arguments.Add(mediaSourceIdArgument); scanPlexCollectionsCommand.Arguments.Add(baseUrlArgument); scanPlexCollectionsCommand.Options.Add(forceOption); + scanPlexCollectionsCommand.Options.Add(deepOption); var scanPlexNetworksCommand = new Command("scan-plex-networks", "Scan Plex networks"); scanPlexNetworksCommand.Arguments.Add(libraryIdArgument); @@ -101,6 +102,7 @@ public class Worker : BackgroundService scanEmbyCollectionsCommand.Arguments.Add(mediaSourceIdArgument); scanEmbyCollectionsCommand.Arguments.Add(baseUrlArgument); scanEmbyCollectionsCommand.Options.Add(forceOption); + scanEmbyCollectionsCommand.Options.Add(deepOption); var scanJellyfinCommand = new Command("scan-jellyfin", "Scan a Jellyfin library"); scanJellyfinCommand.Arguments.Add(libraryIdArgument); @@ -112,6 +114,7 @@ public class Worker : BackgroundService scanJellyfinCollectionsCommand.Arguments.Add(mediaSourceIdArgument); scanJellyfinCollectionsCommand.Arguments.Add(baseUrlArgument); scanJellyfinCollectionsCommand.Options.Add(forceOption); + scanJellyfinCollectionsCommand.Options.Add(deepOption); // Show-specific scanning commands var showIdArgument = new Argument("show-id") @@ -188,6 +191,7 @@ public class Worker : BackgroundService { if (IsScanningEnabled()) { + bool deep = parseResult.GetValue(deepOption); bool force = parseResult.GetValue(forceOption); SetProcessPriority(force); @@ -201,7 +205,7 @@ public class Worker : BackgroundService using IServiceScope scope = _serviceScopeFactory.CreateScope(); IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizePlexCollections(baseUrl, mediaSourceId, force); + var scan = new SynchronizePlexCollections(baseUrl, mediaSourceId, force, deep); await mediator.Send(scan, token); } }); @@ -255,6 +259,7 @@ public class Worker : BackgroundService { if (IsScanningEnabled()) { + bool deep = parseResult.GetValue(deepOption); bool force = parseResult.GetValue(forceOption); SetProcessPriority(force); @@ -268,7 +273,7 @@ public class Worker : BackgroundService using IServiceScope scope = _serviceScopeFactory.CreateScope(); IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeEmbyCollections(baseUrl, mediaSourceId, force); + var scan = new SynchronizeEmbyCollections(baseUrl, mediaSourceId, force, deep); await mediator.Send(scan, token); } }); @@ -300,6 +305,7 @@ public class Worker : BackgroundService { if (IsScanningEnabled()) { + bool deep = parseResult.GetValue(deepOption); bool force = parseResult.GetValue(forceOption); SetProcessPriority(force); @@ -313,7 +319,7 @@ public class Worker : BackgroundService using IServiceScope scope = _serviceScopeFactory.CreateScope(); IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeJellyfinCollections(baseUrl, mediaSourceId, force); + var scan = new SynchronizeJellyfinCollections(baseUrl, mediaSourceId, force, deep); await mediator.Send(scan, token); } }); diff --git a/ErsatzTV/Pages/Libraries.razor b/ErsatzTV/Pages/Libraries.razor index cfb5e26f5..f849a4c89 100644 --- a/ErsatzTV/Pages/Libraries.razor +++ b/ErsatzTV/Pages/Libraries.razor @@ -143,7 +143,19 @@ } else { -
+ if (context.LibraryKind is "Plex" or "Emby" or "Jellyfin") + { + + + + + } + else + { +
+ }