Browse Source

add deep scans for external collections (#2562)

pull/2563/head
Jason Dove 9 months ago committed by GitHub
parent
commit
e590298b93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs
  3. 5
      ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs
  4. 5
      ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs
  5. 2
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs
  6. 5
      ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs
  7. 4
      ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs
  8. 7
      ErsatzTV.Core/Domain/MediaItem/UknownEmbyMediaItem.cs
  9. 4
      ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs
  10. 5
      ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs
  11. 1
      ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs
  12. 2
      ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs
  13. 40
      ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs
  14. 4
      ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs
  15. 2
      ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs
  16. 5
      ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs
  17. 2
      ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs
  18. 5
      ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs
  19. 2
      ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs
  20. 3
      ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs
  21. 78
      ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs
  22. 19
      ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs
  23. 3
      ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs
  24. 12
      ErsatzTV.Scanner/Worker.cs
  25. 22
      ErsatzTV/Pages/Libraries.razor
  26. 6
      ErsatzTV/Services/SchedulerService.cs

4
CHANGELOG.md

@ -28,6 +28,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -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

5
ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs

@ -91,6 +91,11 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler<Synchr @@ -91,6 +91,11 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler<Synchr
arguments.Add("--force");
}
if (request.DeepScan)
{
arguments.Add("--deep");
}
return await base.PerformScan(parameters, arguments, cancellationToken).MapT(_ => Unit.Default);
}
finally

5
ErsatzTV.Application/Emby/Commands/SynchronizeEmbyCollections.cs

@ -2,5 +2,6 @@ using ErsatzTV.Core; @@ -2,5 +2,6 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record SynchronizeEmbyCollections(int EmbyMediaSourceId, bool ForceScan) : IRequest<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;
public record SynchronizeEmbyCollections(int EmbyMediaSourceId, bool ForceScan, bool DeepScan)
: IRequest<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;

5
ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs

@ -91,6 +91,11 @@ public class CallJellyfinCollectionScannerHandler : CallLibraryScannerHandler<Sy @@ -91,6 +91,11 @@ public class CallJellyfinCollectionScannerHandler : CallLibraryScannerHandler<Sy
arguments.Add("--force");
}
if (request.DeepScan)
{
arguments.Add("--deep");
}
return await base.PerformScan(parameters, arguments, cancellationToken).MapT(_ => Unit.Default);
}
finally

2
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs

@ -2,6 +2,6 @@ using ErsatzTV.Core; @@ -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<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;

5
ErsatzTV.Application/Plex/Commands/CallPlexCollectionScannerHandler.cs

@ -91,6 +91,11 @@ public class CallPlexCollectionScannerHandler : CallLibraryScannerHandler<Synchr @@ -91,6 +91,11 @@ public class CallPlexCollectionScannerHandler : CallLibraryScannerHandler<Synchr
arguments.Add("--force");
}
if (request.DeepScan)
{
arguments.Add("--deep");
}
return await base.PerformScan(parameters, arguments, cancellationToken).MapT(_ => Unit.Default);
}
finally

4
ErsatzTV.Application/Plex/Commands/SynchronizePlexCollections.cs

@ -2,5 +2,5 @@ using ErsatzTV.Core; @@ -2,5 +2,5 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Plex;
public record SynchronizePlexCollections(int PlexMediaSourceId, bool ForceScan) : IRequest<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;
public record SynchronizePlexCollections(int PlexMediaSourceId, bool ForceScan, bool DeepScan)
: IRequest<Either<BaseError, Unit>>, IScannerBackgroundServiceRequest;

7
ErsatzTV.Core/Domain/MediaItem/UknownEmbyMediaItem.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Core.Domain;
public class UnknownEmbyMediaItem : MediaItem
{
public string ItemType { get; set; }
public string ItemId { get; set; }
}

4
ErsatzTV.Core/Interfaces/Emby/IEmbyCollectionScanner.cs

@ -2,7 +2,5 @@ @@ -2,7 +2,5 @@
public interface IEmbyCollectionScanner
{
Task<Either<BaseError, Unit>> ScanCollections(
string address,
string apiKey);
Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey, bool deepScan);
}

5
ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinCollectionScanner.cs

@ -2,8 +2,5 @@ @@ -2,8 +2,5 @@
public interface IJellyfinCollectionScanner
{
Task<Either<BaseError, Unit>> ScanCollections(
string address,
string apiKey,
int mediaSourceId);
Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey, int mediaSourceId, bool deepScan);
}

1
ErsatzTV.Core/Interfaces/Plex/IPlexCollectionScanner.cs

@ -8,5 +8,6 @@ public interface IPlexCollectionScanner @@ -8,5 +8,6 @@ public interface IPlexCollectionScanner
Task<Either<BaseError, Unit>> ScanCollections(
PlexConnection connection,
PlexServerAuthToken token,
bool deepScan,
CancellationToken cancellationToken);
}

2
ErsatzTV.Core/Interfaces/Repositories/IEmbyCollectionRepository.cs

@ -8,6 +8,6 @@ public interface IEmbyCollectionRepository @@ -8,6 +8,6 @@ public interface IEmbyCollectionRepository
Task<bool> AddCollection(EmbyCollection collection);
Task<bool> RemoveCollection(EmbyCollection collection);
Task<List<int>> RemoveAllTags(EmbyCollection collection);
Task<int> AddTag(MediaItem item, EmbyCollection collection);
Task<Option<int>> AddTag(MediaItem item, EmbyCollection collection);
Task<bool> SetEtag(EmbyCollection collection);
}

40
ErsatzTV.Infrastructure/Data/Repositories/EmbyCollectionRepository.cs

@ -47,36 +47,36 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -47,36 +47,36 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
// movies
result.AddRange(
await dbContext.Connection.QueryAsync<int>(
@"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<int>(
@"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<int>(
@"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<int>(
@"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 @@ -88,7 +88,7 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
return result;
}
public async Task<int> AddTag(MediaItem item, EmbyCollection collection)
public async Task<Option<int>> AddTag(MediaItem item, EmbyCollection collection)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
switch (item)
@ -97,6 +97,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -97,6 +97,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
int movieId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT Id FROM EmbyMovie WHERE ItemId = @ItemId",
new { movie.ItemId });
if (movieId <= 0)
{
return Option<int>.None;
}
await dbContext.Connection.ExecuteAsync(
@"INSERT INTO Tag (Name, ExternalCollectionId, MovieMetadataId)
SELECT @Name, @ItemId, Id FROM
@ -107,6 +112,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -107,6 +112,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
int showId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT Id FROM EmbyShow WHERE ItemId = @ItemId",
new { show.ItemId });
if (showId <= 0)
{
return Option<int>.None;
}
await dbContext.Connection.ExecuteAsync(
@"INSERT INTO Tag (Name, ExternalCollectionId, ShowMetadataId)
SELECT @Name, @ItemId, Id FROM
@ -117,6 +127,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -117,6 +127,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
int seasonId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT Id FROM EmbySeason WHERE ItemId = @ItemId",
new { season.ItemId });
if (seasonId <= 0)
{
return Option<int>.None;
}
await dbContext.Connection.ExecuteAsync(
@"INSERT INTO Tag (Name, ExternalCollectionId, SeasonMetadataId)
SELECT @Name, @ItemId, Id FROM
@ -127,6 +142,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -127,6 +142,11 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
int episodeId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT Id FROM EmbyEpisode WHERE ItemId = @ItemId",
new { episode.ItemId });
if (episodeId <= 0)
{
return Option<int>.None;
}
await dbContext.Connection.ExecuteAsync(
@"INSERT INTO Tag (Name, ExternalCollectionId, EpisodeMetadataId)
SELECT @Name, @ItemId, Id FROM
@ -134,7 +154,7 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository @@ -134,7 +154,7 @@ public class EmbyCollectionRepository : IEmbyCollectionRepository
new { collection.Name, collection.ItemId, EpisodeId = episodeId });
return episodeId;
default:
return 0;
return Option<int>.None;
}
}

4
ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs

@ -137,6 +137,8 @@ public class EmbyApiClient : IEmbyApiClient @@ -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 @@ -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)

2
ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollections.cs

@ -2,5 +2,5 @@ @@ -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<Either<BaseError, Unit>>;

5
ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs

@ -52,6 +52,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmby @@ -52,6 +52,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmby
connectionParameters,
connectionParameters.MediaSource,
request.ForceScan,
request.DeepScan,
libraryRefreshInterval,
request.BaseUrl));
}
@ -97,7 +98,8 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmby @@ -97,7 +98,8 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmby
{
Either<BaseError, Unit> 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<SynchronizeEmby @@ -115,6 +117,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmby
ConnectionParameters ConnectionParameters,
EmbyMediaSource MediaSource,
bool ForceScan,
bool DeepScan,
int LibraryRefreshInterval,
string BaseUrl);

2
ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs

@ -2,5 +2,5 @@ @@ -2,5 +2,5 @@
namespace ErsatzTV.Scanner.Application.Jellyfin;
public record SynchronizeJellyfinCollections(string BaseUrl, int JellyfinMediaSourceId, bool ForceScan)
public record SynchronizeJellyfinCollections(string BaseUrl, int JellyfinMediaSourceId, bool ForceScan, bool DeepScan)
: IRequest<Either<BaseError, Unit>>;

5
ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs

@ -54,6 +54,7 @@ public class @@ -54,6 +54,7 @@ public class
connectionParameters,
connectionParameters.MediaSource,
request.ForceScan,
request.DeepScan,
libraryRefreshInterval,
request.BaseUrl));
}
@ -99,7 +100,8 @@ public class @@ -99,7 +100,8 @@ public class
Either<BaseError, Unit> 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 @@ -117,6 +119,7 @@ public class
ConnectionParameters ConnectionParameters,
JellyfinMediaSource MediaSource,
bool ForceScan,
bool DeepScan,
int LibraryRefreshInterval,
string BaseUrl);

2
ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollections.cs

@ -2,5 +2,5 @@ @@ -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<Either<BaseError, Unit>>;

3
ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs

@ -52,6 +52,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex @@ -52,6 +52,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex
connectionParameters,
connectionParameters.PlexMediaSource,
request.ForceScan,
request.DeepScan,
libraryRefreshInterval,
request.BaseUrl));
}
@ -99,6 +100,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex @@ -99,6 +100,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex
Either<BaseError, Unit> result = await _scanner.ScanCollections(
parameters.ConnectionParameters.ActiveConnection,
parameters.ConnectionParameters.PlexServerAuthToken,
parameters.DeepScan,
cancellationToken);
if (result.IsRight)
@ -117,6 +119,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex @@ -117,6 +119,7 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlex
ConnectionParameters ConnectionParameters,
PlexMediaSource MediaSource,
bool ForceScan,
bool DeepScan,
int LibraryRefreshInterval,
string BaseUrl);

78
ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs

@ -26,7 +26,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner @@ -26,7 +26,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
_logger = logger;
}
public async Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey)
public async Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey, bool deepScan)
{
try
{
@ -46,13 +46,13 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner @@ -46,13 +46,13 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
Option<EmbyCollection> 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 @@ -98,16 +98,74 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
List<int> removedIds = await _embyCollectionRepository.RemoveAllTags(collection);
var movies = 0;
var shows = 0;
var seasons = 0;
var episodes = 0;
var otherTypes = new Dictionary<string, int>();
// sync tags on items
var addedIds = new List<int>();
await foreach ((MediaItem item, int _) in items)
{
addedIds.Add(await _embyCollectionRepository.AddTag(item, collection));
Option<int> 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");

19
ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs

@ -26,7 +26,11 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner @@ -26,7 +26,11 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
_logger = logger;
}
public async Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey, int mediaSourceId)
public async Task<Either<BaseError, Unit>> ScanCollections(
string address,
string apiKey,
int mediaSourceId,
bool deepScan)
{
try
{
@ -48,12 +52,13 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner @@ -48,12 +52,13 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
Option<JellyfinCollection> 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)

3
ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs

@ -30,6 +30,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner @@ -30,6 +30,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner
public async Task<Either<BaseError, Unit>> ScanCollections(
PlexConnection connection,
PlexServerAuthToken token,
bool deepScan,
CancellationToken cancellationToken)
{
try
@ -49,7 +50,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner @@ -49,7 +50,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner
Option<PlexCollection> 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);

12
ErsatzTV.Scanner/Worker.cs

@ -85,6 +85,7 @@ public class Worker : BackgroundService @@ -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 @@ -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 @@ -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<int>("show-id")
@ -188,6 +191,7 @@ public class Worker : BackgroundService @@ -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 @@ -201,7 +205,7 @@ public class Worker : BackgroundService
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
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 @@ -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 @@ -268,7 +273,7 @@ public class Worker : BackgroundService
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
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 @@ -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 @@ -313,7 +319,7 @@ public class Worker : BackgroundService
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var scan = new SynchronizeJellyfinCollections(baseUrl, mediaSourceId, force);
var scan = new SynchronizeJellyfinCollections(baseUrl, mediaSourceId, force, deep);
await mediator.Send(scan, token);
}
});

22
ErsatzTV/Pages/Libraries.razor

@ -143,7 +143,19 @@ @@ -143,7 +143,19 @@
}
else
{
<div style="width: 48px"></div>
if (context.LibraryKind is "Plex" or "Emby" or "Jellyfin")
{
<MudTooltip Text="Deep Scan Collections">
<MudIconButton Icon="@Icons.Material.Filled.FindReplace"
Disabled="@AreCollectionsLocked(context.LibraryKind)"
OnClick="@(_ => ScanExternalCollections(context, true))">
</MudIconButton>
</MudTooltip>
}
else
{
<div style="width: 48px"></div>
}
<MudTooltip Text="Scan Collections">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Disabled="@AreCollectionsLocked(context.LibraryKind)"
@ -235,7 +247,7 @@ @@ -235,7 +247,7 @@
}
}
private async Task ScanExternalCollections(LibraryViewModel library)
private async Task ScanExternalCollections(LibraryViewModel library, bool deepScan = false)
{
var token = _cts?.Token ?? CancellationToken.None;
switch (library.LibraryKind.ToLowerInvariant())
@ -243,21 +255,21 @@ @@ -243,21 +255,21 @@
case "emby":
if (Locker.LockEmbyCollections())
{
await ScannerWorkerChannel.WriteAsync(new SynchronizeEmbyCollections(library.MediaSourceId, true), token);
await ScannerWorkerChannel.WriteAsync(new SynchronizeEmbyCollections(library.MediaSourceId, true, deepScan), token);
}
break;
case "jellyfin":
if (Locker.LockJellyfinCollections())
{
await ScannerWorkerChannel.WriteAsync(new SynchronizeJellyfinCollections(library.MediaSourceId, true), token);
await ScannerWorkerChannel.WriteAsync(new SynchronizeJellyfinCollections(library.MediaSourceId, true, deepScan), token);
}
break;
case "plex":
if (Locker.LockPlexCollections())
{
await ScannerWorkerChannel.WriteAsync(new SynchronizePlexCollections(library.MediaSourceId, true), token);
await ScannerWorkerChannel.WriteAsync(new SynchronizePlexCollections(library.MediaSourceId, true, deepScan), token);
}
break;

6
ErsatzTV/Services/SchedulerService.cs

@ -268,7 +268,7 @@ public class SchedulerService : BackgroundService @@ -268,7 +268,7 @@ public class SchedulerService : BackgroundService
foreach (int mediaSourceId in mediaSourceIds)
{
await _scannerWorkerChannel.WriteAsync(
new SynchronizePlexCollections(mediaSourceId, false),
new SynchronizePlexCollections(mediaSourceId, false, false),
cancellationToken);
}
}
@ -295,7 +295,7 @@ public class SchedulerService : BackgroundService @@ -295,7 +295,7 @@ public class SchedulerService : BackgroundService
foreach (int mediaSourceId in mediaSourceIds)
{
await _scannerWorkerChannel.WriteAsync(
new SynchronizeJellyfinCollections(mediaSourceId, false),
new SynchronizeJellyfinCollections(mediaSourceId, false, false),
cancellationToken);
}
}
@ -322,7 +322,7 @@ public class SchedulerService : BackgroundService @@ -322,7 +322,7 @@ public class SchedulerService : BackgroundService
foreach (int mediaSourceId in mediaSourceIds)
{
await _scannerWorkerChannel.WriteAsync(
new SynchronizeEmbyCollections(mediaSourceId, false),
new SynchronizeEmbyCollections(mediaSourceId, false, false),
cancellationToken);
}
}

Loading…
Cancel
Save