Browse Source

update media server scanning and paging (#1770)

* update media server scanning and paging

* remove unused types
pull/1771/head
Jason Dove 2 years ago committed by GitHub
parent
commit
f41fa669be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 11
      ErsatzTV.Core/Emby/EmbyItemType.cs
  3. 18
      ErsatzTV.Core/Interfaces/Emby/IEmbyApiClient.cs
  4. 20
      ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs
  5. 27
      ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs
  6. 11
      ErsatzTV.Core/Jellyfin/JellyfinItemType.cs
  7. 66
      ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs
  8. 15
      ErsatzTV.Infrastructure/Emby/IEmbyApi.cs
  9. 8
      ErsatzTV.Infrastructure/Emby/Models/EmbyItemsCountsResponse.cs
  10. 19
      ErsatzTV.Infrastructure/Jellyfin/IJellyfinApi.cs
  11. 80
      ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs
  12. 66
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs
  13. 8
      ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs
  14. 11
      ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs
  15. 35
      ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs
  16. 8
      ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs
  17. 13
      ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs
  18. 41
      ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs
  19. 40
      ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs
  20. 99
      ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs
  21. 6
      ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs
  22. 10
      ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs
  23. 32
      ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs

2
CHANGELOG.md

@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Changed
- Remove some unnecessary API calls related to media server scanning and paging
## [0.8.7-beta] - 2024-06-26 ## [0.8.7-beta] - 2024-06-26
### Added ### Added

11
ErsatzTV.Core/Emby/EmbyItemType.cs

@ -1,11 +0,0 @@
namespace ErsatzTV.Core.Emby;
public static class EmbyItemType
{
public static readonly string Movie = "Movie";
public static readonly string Show = "Series";
public static readonly string Season = "Season";
public static readonly string Episode = "Episode";
public static readonly string Collection = "BoxSet";
public static readonly string CollectionItems = "Movie,Series,Season,Episode";
}

18
ErsatzTV.Core/Interfaces/Emby/IEmbyApiClient.cs

@ -8,32 +8,26 @@ public interface IEmbyApiClient
Task<Either<BaseError, EmbyServerInformation>> GetServerInformation(string address, string apiKey); Task<Either<BaseError, EmbyServerInformation>> GetServerInformation(string address, string apiKey);
Task<Either<BaseError, List<EmbyLibrary>>> GetLibraries(string address, string apiKey); Task<Either<BaseError, List<EmbyLibrary>>> GetLibraries(string address, string apiKey);
IAsyncEnumerable<EmbyMovie> GetMovieLibraryItems(string address, string apiKey, EmbyLibrary library); IAsyncEnumerable<Tuple<EmbyMovie, int>> GetMovieLibraryItems(string address, string apiKey, EmbyLibrary library);
IAsyncEnumerable<EmbyShow> GetShowLibraryItems(string address, string apiKey, EmbyLibrary library); IAsyncEnumerable<Tuple<EmbyShow, int>> GetShowLibraryItems(string address, string apiKey, EmbyLibrary library);
IAsyncEnumerable<EmbySeason> GetSeasonLibraryItems( IAsyncEnumerable<Tuple<EmbySeason, int>> GetSeasonLibraryItems(
string address, string address,
string apiKey, string apiKey,
EmbyLibrary library, EmbyLibrary library,
string showId); string showId);
IAsyncEnumerable<EmbyEpisode> GetEpisodeLibraryItems( IAsyncEnumerable<Tuple<EmbyEpisode, int>> GetEpisodeLibraryItems(
string address, string address,
string apiKey, string apiKey,
EmbyLibrary library, EmbyLibrary library,
string showId, string showId,
string seasonId); string seasonId);
IAsyncEnumerable<EmbyCollection> GetCollectionLibraryItems(string address, string apiKey); IAsyncEnumerable<Tuple<EmbyCollection, int>> GetCollectionLibraryItems(string address, string apiKey);
IAsyncEnumerable<MediaItem> GetCollectionItems(string address, string apiKey, string collectionId); IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(string address, string apiKey, string collectionId);
Task<Either<BaseError, int>> GetLibraryItemCount(
string address,
string apiKey,
string parentId,
string includeItemTypes);
Task<Either<BaseError, MediaVersion>> GetPlaybackInfo( Task<Either<BaseError, MediaVersion>> GetPlaybackInfo(
string address, string address,

20
ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs

@ -9,38 +9,30 @@ public interface IJellyfinApiClient
Task<Either<BaseError, List<JellyfinLibrary>>> GetLibraries(string address, string apiKey); Task<Either<BaseError, List<JellyfinLibrary>>> GetLibraries(string address, string apiKey);
Task<Either<BaseError, string>> GetAdminUserId(string address, string apiKey); Task<Either<BaseError, string>> GetAdminUserId(string address, string apiKey);
IAsyncEnumerable<JellyfinMovie> GetMovieLibraryItems(string address, string apiKey, JellyfinLibrary library); IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(string address, string apiKey, JellyfinLibrary library);
IAsyncEnumerable<JellyfinShow> GetShowLibraryItems(string address, string apiKey, JellyfinLibrary library); IAsyncEnumerable<Tuple<JellyfinShow, int>> GetShowLibraryItems(string address, string apiKey, JellyfinLibrary library);
IAsyncEnumerable<JellyfinSeason> GetSeasonLibraryItems( IAsyncEnumerable<Tuple<JellyfinSeason, int>> GetSeasonLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library, JellyfinLibrary library,
string showId); string showId);
IAsyncEnumerable<JellyfinEpisode> GetEpisodeLibraryItems( IAsyncEnumerable<Tuple<JellyfinEpisode, int>> GetEpisodeLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library, JellyfinLibrary library,
string seasonId); string seasonId);
IAsyncEnumerable<JellyfinCollection> GetCollectionLibraryItems(string address, string apiKey, int mediaSourceId); IAsyncEnumerable<Tuple<JellyfinCollection, int>> GetCollectionLibraryItems(string address, string apiKey, int mediaSourceId);
IAsyncEnumerable<MediaItem> GetCollectionItems( IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(
string address, string address,
string apiKey, string apiKey,
int mediaSourceId, int mediaSourceId,
string collectionId); string collectionId);
Task<Either<BaseError, int>> GetLibraryItemCount(
string address,
string apiKey,
JellyfinLibrary library,
string parentId,
string includeItemTypes,
bool excludeFolders);
Task<Either<BaseError, MediaVersion>> GetPlaybackInfo( Task<Either<BaseError, MediaVersion>> GetPlaybackInfo(
string address, string address,
string apiKey, string apiKey,

27
ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs

@ -13,33 +13,23 @@ public interface IPlexServerApiClient
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);
IAsyncEnumerable<PlexMovie> GetMovieLibraryContents( IAsyncEnumerable<Tuple<PlexMovie, int>> GetMovieLibraryContents(
PlexLibrary library, PlexLibrary library,
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);
IAsyncEnumerable<PlexShow> GetShowLibraryContents( IAsyncEnumerable<Tuple<PlexShow, int>> GetShowLibraryContents(
PlexLibrary library, PlexLibrary library,
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);
Task<Either<BaseError, int>> CountShowSeasons( IAsyncEnumerable<Tuple<PlexSeason, int>> GetShowSeasons(
PlexShow show,
PlexConnection connection,
PlexServerAuthToken token);
IAsyncEnumerable<PlexSeason> GetShowSeasons(
PlexLibrary library, PlexLibrary library,
PlexShow show, PlexShow show,
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);
Task<Either<BaseError, int>> CountSeasonEpisodes( IAsyncEnumerable<Tuple<PlexEpisode, int>> GetSeasonEpisodes(
PlexSeason season,
PlexConnection connection,
PlexServerAuthToken token);
IAsyncEnumerable<PlexEpisode> GetSeasonEpisodes(
PlexLibrary library, PlexLibrary library,
PlexSeason season, PlexSeason season,
PlexConnection connection, PlexConnection connection,
@ -63,17 +53,12 @@ public interface IPlexServerApiClient
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);
Task<Either<BaseError, int>> GetLibraryItemCount( IAsyncEnumerable<Tuple<PlexCollection, int>> GetAllCollections(
PlexLibrary library,
PlexConnection connection,
PlexServerAuthToken token);
IAsyncEnumerable<PlexCollection> GetAllCollections(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
CancellationToken cancellationToken); CancellationToken cancellationToken);
IAsyncEnumerable<MediaItem> GetCollectionItems( IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
string key, string key,

11
ErsatzTV.Core/Jellyfin/JellyfinItemType.cs

@ -1,11 +0,0 @@
namespace ErsatzTV.Core.Jellyfin;
public static class JellyfinItemType
{
public static readonly string Movie = "Movie";
public static readonly string Show = "Series";
public static readonly string Season = "Season";
public static readonly string Episode = "Episode";
public static readonly string Collection = "BoxSet";
public static readonly string CollectionItems = "Movie,Series,Season,Episode";
}

66
ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs

@ -69,13 +69,11 @@ public class EmbyApiClient : IEmbyApiClient
} }
} }
public IAsyncEnumerable<EmbyMovie> GetMovieLibraryItems(string address, string apiKey, EmbyLibrary library) public IAsyncEnumerable<Tuple<EmbyMovie, int>> GetMovieLibraryItems(string address, string apiKey, EmbyLibrary library)
=> GetPagedLibraryContents( => GetPagedLibraryContents(
address, address,
apiKey,
library, library,
library.ItemId, library.ItemId,
EmbyItemType.Movie,
(service, itemId, skip, pageSize) => service.GetMovieLibraryItems( (service, itemId, skip, pageSize) => service.GetMovieLibraryItems(
apiKey, apiKey,
itemId, itemId,
@ -83,13 +81,11 @@ public class EmbyApiClient : IEmbyApiClient
limit: pageSize), limit: pageSize),
(maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToMovie(lib, item)).Flatten()); (maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToMovie(lib, item)).Flatten());
public IAsyncEnumerable<EmbyShow> GetShowLibraryItems(string address, string apiKey, EmbyLibrary library) public IAsyncEnumerable<Tuple<EmbyShow, int>> GetShowLibraryItems(string address, string apiKey, EmbyLibrary library)
=> GetPagedLibraryContents( => GetPagedLibraryContents(
address, address,
apiKey,
library, library,
library.ItemId, library.ItemId,
EmbyItemType.Show,
(service, itemId, skip, pageSize) => service.GetShowLibraryItems( (service, itemId, skip, pageSize) => service.GetShowLibraryItems(
apiKey, apiKey,
itemId, itemId,
@ -97,16 +93,14 @@ public class EmbyApiClient : IEmbyApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToShow(item)); (_, item) => ProjectToShow(item));
public IAsyncEnumerable<EmbySeason> GetSeasonLibraryItems( public IAsyncEnumerable<Tuple<EmbySeason, int>> GetSeasonLibraryItems(
string address, string address,
string apiKey, string apiKey,
EmbyLibrary library, EmbyLibrary library,
string showId) => GetPagedLibraryContents( string showId) => GetPagedLibraryContents(
address, address,
apiKey,
library, library,
showId, showId,
EmbyItemType.Season,
(service, itemId, skip, pageSize) => service.GetSeasonLibraryItems( (service, itemId, skip, pageSize) => service.GetSeasonLibraryItems(
apiKey, apiKey,
itemId, itemId,
@ -114,17 +108,15 @@ public class EmbyApiClient : IEmbyApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToSeason(item)); (_, item) => ProjectToSeason(item));
public IAsyncEnumerable<EmbyEpisode> GetEpisodeLibraryItems( public IAsyncEnumerable<Tuple<EmbyEpisode, int>> GetEpisodeLibraryItems(
string address, string address,
string apiKey, string apiKey,
EmbyLibrary library, EmbyLibrary library,
string showId, string showId,
string seasonId) => GetPagedLibraryContents( string seasonId) => GetPagedLibraryContents(
address, address,
apiKey,
library, library,
seasonId, seasonId,
EmbyItemType.Episode,
(service, _, skip, pageSize) => service.GetEpisodeLibraryItems( (service, _, skip, pageSize) => service.GetEpisodeLibraryItems(
apiKey, apiKey,
showId, showId,
@ -133,7 +125,7 @@ public class EmbyApiClient : IEmbyApiClient
limit: pageSize), limit: pageSize),
(maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToEpisode(lib, item)).Flatten()); (maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToEpisode(lib, item)).Flatten());
public IAsyncEnumerable<EmbyCollection> GetCollectionLibraryItems(string address, string apiKey) public IAsyncEnumerable<Tuple<EmbyCollection, int>> GetCollectionLibraryItems(string address, string apiKey)
{ {
// TODO: should we enumerate collection libraries here? // TODO: should we enumerate collection libraries here?
@ -141,10 +133,8 @@ public class EmbyApiClient : IEmbyApiClient
{ {
return GetPagedLibraryContents( return GetPagedLibraryContents(
address, address,
apiKey,
None, None,
itemId, itemId,
EmbyItemType.Collection,
(service, _, skip, pageSize) => service.GetCollectionLibraryItems( (service, _, skip, pageSize) => service.GetCollectionLibraryItems(
apiKey, apiKey,
itemId, itemId,
@ -153,19 +143,17 @@ public class EmbyApiClient : IEmbyApiClient
(_, item) => ProjectToCollection(item)); (_, item) => ProjectToCollection(item));
} }
return AsyncEnumerable.Empty<EmbyCollection>(); return AsyncEnumerable.Empty<Tuple<EmbyCollection, int>>();
} }
public IAsyncEnumerable<MediaItem> GetCollectionItems( public IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(
string address, string address,
string apiKey, string apiKey,
string collectionId) => string collectionId) =>
GetPagedLibraryContents( GetPagedLibraryContents(
address, address,
apiKey,
None, None,
collectionId, collectionId,
EmbyItemType.CollectionItems,
(service, _, skip, pageSize) => service.GetCollectionItems( (service, _, skip, pageSize) => service.GetCollectionItems(
apiKey, apiKey,
collectionId, collectionId,
@ -173,25 +161,6 @@ public class EmbyApiClient : IEmbyApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToCollectionMediaItem(item)); (_, item) => ProjectToCollectionMediaItem(item));
public async Task<Either<BaseError, int>> GetLibraryItemCount(
string address,
string apiKey,
string parentId,
string includeItemTypes)
{
try
{
IEmbyApi service = RestService.For<IEmbyApi>(address);
EmbyLibraryItemsResponse items = await service.GetLibraryStats(apiKey, parentId, includeItemTypes);
return items.TotalRecordCount;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting Emby library item count");
return BaseError.New(ex.Message);
}
}
public async Task<Either<BaseError, MediaVersion>> GetPlaybackInfo( public async Task<Either<BaseError, MediaVersion>> GetPlaybackInfo(
string address, string address,
string apiKey, string apiKey,
@ -212,36 +181,31 @@ public class EmbyApiClient : IEmbyApiClient
} }
} }
private static async IAsyncEnumerable<TItem> GetPagedLibraryContents<TItem>( private static async IAsyncEnumerable<Tuple<TItem, int>> GetPagedLibraryContents<TItem>(
string address, string address,
string apiKey,
Option<EmbyLibrary> maybeLibrary, Option<EmbyLibrary> maybeLibrary,
string parentId, string parentId,
string itemType,
Func<IEmbyApi, string, int, int, Task<EmbyLibraryItemsResponse>> getItems, Func<IEmbyApi, string, int, int, Task<EmbyLibraryItemsResponse>> getItems,
Func<Option<EmbyLibrary>, EmbyLibraryItemResponse, Option<TItem>> mapper) Func<Option<EmbyLibrary>, EmbyLibraryItemResponse, Option<TItem>> mapper)
{ {
IEmbyApi service = RestService.For<IEmbyApi>(address); IEmbyApi service = RestService.For<IEmbyApi>(address);
int size = await service
.GetLibraryStats(apiKey, parentId, itemType)
.Map(r => r.TotalRecordCount);
const int PAGE_SIZE = 10; const int PAGE_SIZE = 10;
int pages = (size - 1) / PAGE_SIZE + 1; int pages = int.MaxValue;
for (var i = 0; i < pages; i++) for (var i = 0; i < pages; i++)
{ {
int skip = i * PAGE_SIZE; int skip = i * PAGE_SIZE;
Task<IEnumerable<TItem>> result = getItems(service, parentId, skip, PAGE_SIZE) EmbyLibraryItemsResponse result = await getItems(service, parentId, skip, PAGE_SIZE);
.Map(items => items.Items.Map(item => mapper(maybeLibrary, item)).Somes());
// update page count
pages = Math.Min(pages, (result.TotalRecordCount - 1) / PAGE_SIZE + 1);
#pragma warning disable VSTHRD003 #pragma warning disable VSTHRD003
foreach (TItem item in await result) foreach (TItem item in result.Items.Map(item => mapper(maybeLibrary, item)).Somes())
#pragma warning restore VSTHRD003 #pragma warning restore VSTHRD003
{ {
yield return item; yield return new Tuple<TItem, int>(item, result.TotalRecordCount);
} }
} }
} }

15
ErsatzTV.Infrastructure/Emby/IEmbyApi.cs

@ -17,21 +17,6 @@ public interface IEmbyApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey); string apiKey);
[Get("/Items")]
public Task<EmbyLibraryItemsResponse> GetLibraryStats(
[Header("X-Emby-Token")]
string apiKey,
[Query]
string parentId,
[Query]
string includeItemTypes,
[Query]
bool recursive = true,
[Query]
int startIndex = 0,
[Query]
int limit = 0);
[Get("/Items?sortOrder=Ascending&sortBy=SortName")] [Get("/Items?sortOrder=Ascending&sortBy=SortName")]
public Task<EmbyLibraryItemsResponse> GetMovieLibraryItems( public Task<EmbyLibraryItemsResponse> GetMovieLibraryItems(
[Header("X-Emby-Token")] [Header("X-Emby-Token")]

8
ErsatzTV.Infrastructure/Emby/Models/EmbyItemsCountsResponse.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.Infrastructure.Emby.Models;
public class EmbyItemsCountsResponse
{
public int MovieCount { get; set; }
public int SeriesCount { get; set; }
public int EpisodeCount { get; set; }
}

19
ErsatzTV.Infrastructure/Jellyfin/IJellyfinApi.cs

@ -22,25 +22,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey); string apiKey);
[Get("/Items")]
public Task<JellyfinLibraryItemsResponse> GetLibraryStats(
[Header("X-Emby-Token")]
string apiKey,
[Query]
string userId,
[Query]
string parentId,
[Query]
string includeItemTypes,
[Query]
bool recursive = true,
[Query]
string filters = "IsNotFolder",
[Query]
int startIndex = 0,
[Query]
int limit = 0);
[Get("/Items?sortOrder=Ascending&sortBy=SortName")] [Get("/Items?sortOrder=Ascending&sortBy=SortName")]
public Task<JellyfinLibraryItemsResponse> GetMovieLibraryItems( public Task<JellyfinLibraryItemsResponse> GetMovieLibraryItems(
[Header("X-Emby-Token")] [Header("X-Emby-Token")]

80
ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs

@ -93,17 +93,15 @@ public class JellyfinApiClient : IJellyfinApiClient
} }
} }
public IAsyncEnumerable<JellyfinMovie> GetMovieLibraryItems( public IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library) => JellyfinLibrary library) =>
GetPagedLibraryItems( GetPagedLibraryItems(
address, address,
apiKey,
library, library,
library.MediaSourceId, library.MediaSourceId,
library.ItemId, library.ItemId,
JellyfinItemType.Movie,
(service, userId, itemId, skip, pageSize) => service.GetMovieLibraryItems( (service, userId, itemId, skip, pageSize) => service.GetMovieLibraryItems(
apiKey, apiKey,
userId, userId,
@ -112,17 +110,15 @@ public class JellyfinApiClient : IJellyfinApiClient
limit: pageSize), limit: pageSize),
(maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToMovie(lib, item)).Flatten()); (maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToMovie(lib, item)).Flatten());
public IAsyncEnumerable<JellyfinShow> GetShowLibraryItems( public IAsyncEnumerable<Tuple<JellyfinShow, int>> GetShowLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library) => JellyfinLibrary library) =>
GetPagedLibraryItems( GetPagedLibraryItems(
address, address,
apiKey,
library, library,
library.MediaSourceId, library.MediaSourceId,
library.ItemId, library.ItemId,
JellyfinItemType.Show,
(service, userId, itemId, skip, pageSize) => service.GetShowLibraryItems( (service, userId, itemId, skip, pageSize) => service.GetShowLibraryItems(
apiKey, apiKey,
userId, userId,
@ -131,18 +127,16 @@ public class JellyfinApiClient : IJellyfinApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToShow(item)); (_, item) => ProjectToShow(item));
public IAsyncEnumerable<JellyfinSeason> GetSeasonLibraryItems( public IAsyncEnumerable<Tuple<JellyfinSeason, int>> GetSeasonLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library, JellyfinLibrary library,
string showId) => string showId) =>
GetPagedLibraryItems( GetPagedLibraryItems(
address, address,
apiKey,
library, library,
library.MediaSourceId, library.MediaSourceId,
showId, showId,
JellyfinItemType.Season,
(service, userId, _, skip, pageSize) => service.GetSeasonLibraryItems( (service, userId, _, skip, pageSize) => service.GetSeasonLibraryItems(
apiKey, apiKey,
userId, userId,
@ -151,18 +145,16 @@ public class JellyfinApiClient : IJellyfinApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToSeason(item)); (_, item) => ProjectToSeason(item));
public IAsyncEnumerable<JellyfinEpisode> GetEpisodeLibraryItems( public IAsyncEnumerable<Tuple<JellyfinEpisode, int>> GetEpisodeLibraryItems(
string address, string address,
string apiKey, string apiKey,
JellyfinLibrary library, JellyfinLibrary library,
string seasonId) => string seasonId) =>
GetPagedLibraryItems( GetPagedLibraryItems(
address, address,
apiKey,
library, library,
library.MediaSourceId, library.MediaSourceId,
seasonId, seasonId,
JellyfinItemType.Episode,
(service, userId, _, skip, pageSize) => service.GetEpisodeLibraryItems( (service, userId, _, skip, pageSize) => service.GetEpisodeLibraryItems(
apiKey, apiKey,
userId, userId,
@ -171,7 +163,7 @@ public class JellyfinApiClient : IJellyfinApiClient
limit: pageSize), limit: pageSize),
(maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToEpisode(lib, item)).Flatten()); (maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToEpisode(lib, item)).Flatten());
public IAsyncEnumerable<JellyfinCollection> GetCollectionLibraryItems( public IAsyncEnumerable<Tuple<JellyfinCollection, int>> GetCollectionLibraryItems(
string address, string address,
string apiKey, string apiKey,
int mediaSourceId) int mediaSourceId)
@ -182,11 +174,9 @@ public class JellyfinApiClient : IJellyfinApiClient
{ {
return GetPagedLibraryItems( return GetPagedLibraryItems(
address, address,
apiKey,
None, None,
mediaSourceId, mediaSourceId,
itemId, itemId,
JellyfinItemType.Collection,
(service, userId, _, skip, pageSize) => service.GetCollectionLibraryItems( (service, userId, _, skip, pageSize) => service.GetCollectionLibraryItems(
apiKey, apiKey,
userId, userId,
@ -196,21 +186,19 @@ public class JellyfinApiClient : IJellyfinApiClient
(_, item) => ProjectToCollection(item)); (_, item) => ProjectToCollection(item));
} }
return AsyncEnumerable.Empty<JellyfinCollection>(); return AsyncEnumerable.Empty<Tuple<JellyfinCollection, int>>();
} }
public IAsyncEnumerable<MediaItem> GetCollectionItems( public IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(
string address, string address,
string apiKey, string apiKey,
int mediaSourceId, int mediaSourceId,
string collectionId) => string collectionId) =>
GetPagedLibraryItems( GetPagedLibraryItems(
address, address,
apiKey,
None, None,
mediaSourceId, mediaSourceId,
collectionId, collectionId,
JellyfinItemType.CollectionItems,
(service, userId, _, skip, pageSize) => service.GetCollectionItems( (service, userId, _, skip, pageSize) => service.GetCollectionItems(
apiKey, apiKey,
userId, userId,
@ -219,37 +207,6 @@ public class JellyfinApiClient : IJellyfinApiClient
limit: pageSize), limit: pageSize),
(_, item) => ProjectToCollectionMediaItem(item)); (_, item) => ProjectToCollectionMediaItem(item));
public async Task<Either<BaseError, int>> GetLibraryItemCount(
string address,
string apiKey,
JellyfinLibrary library,
string parentId,
string includeItemTypes,
bool excludeFolders)
{
try
{
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{library.MediaSourceId}", out string userId))
{
IJellyfinApi service = RestService.For<IJellyfinApi>(address);
JellyfinLibraryItemsResponse items = await service.GetLibraryStats(
apiKey,
userId,
parentId,
includeItemTypes,
filters: excludeFolders ? "IsNotFolder" : null);
return items.TotalRecordCount;
}
return BaseError.New("Jellyfin admin user id is not available");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting jellyfin library item count");
return BaseError.New(ex.Message);
}
}
public async Task<Either<BaseError, MediaVersion>> GetPlaybackInfo( public async Task<Either<BaseError, MediaVersion>> GetPlaybackInfo(
string address, string address,
string apiKey, string apiKey,
@ -275,40 +232,33 @@ public class JellyfinApiClient : IJellyfinApiClient
} }
} }
private async IAsyncEnumerable<TItem> GetPagedLibraryItems<TItem>( private async IAsyncEnumerable<Tuple<TItem, int>> GetPagedLibraryItems<TItem>(
string address, string address,
string apiKey,
Option<JellyfinLibrary> maybeLibrary, Option<JellyfinLibrary> maybeLibrary,
int mediaSourceId, int mediaSourceId,
string parentId, string parentId,
string itemType,
Func<IJellyfinApi, string, string, int, int, Task<JellyfinLibraryItemsResponse>> getItems, Func<IJellyfinApi, string, string, int, int, Task<JellyfinLibraryItemsResponse>> getItems,
Func<Option<JellyfinLibrary>, JellyfinLibraryItemResponse, Option<TItem>> mapper) Func<Option<JellyfinLibrary>, JellyfinLibraryItemResponse, Option<TItem>> mapper)
{ {
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{mediaSourceId}", out string userId)) if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{mediaSourceId}", out string userId))
{ {
IJellyfinApi service = RestService.For<IJellyfinApi>(address); IJellyfinApi service = RestService.For<IJellyfinApi>(address);
string filters = itemType == JellyfinItemType.Movie || itemType == JellyfinItemType.Episode
? "IsNotFolder"
: null;
int size = await service
.GetLibraryStats(apiKey, userId, parentId, itemType, filters: filters)
.Map(r => r.TotalRecordCount);
const int PAGE_SIZE = 10; const int PAGE_SIZE = 10;
int pages = (size - 1) / PAGE_SIZE + 1; int pages = int.MaxValue;
for (var i = 0; i < pages; i++) for (var i = 0; i < pages; i++)
{ {
int skip = i * PAGE_SIZE; int skip = i * PAGE_SIZE;
Task<IEnumerable<TItem>> result = getItems(service, userId, parentId, skip, PAGE_SIZE) JellyfinLibraryItemsResponse result = await getItems(service, userId, parentId, skip, PAGE_SIZE);
.Map(items => items.Items.Map(item => mapper(maybeLibrary, item)).Somes());
// update page count
pages = Math.Min(pages, (result.TotalRecordCount - 1) / PAGE_SIZE + 1);
foreach (TItem item in await result) foreach (TItem item in result.Items.Map(item => mapper(maybeLibrary, item)).Somes())
{ {
yield return item; yield return new Tuple<TItem, int>(item, result.TotalRecordCount);
} }
} }
} }

66
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -66,7 +66,7 @@ public class PlexServerApiClient : IPlexServerApiClient
} }
} }
public IAsyncEnumerable<PlexMovie> GetMovieLibraryContents( public IAsyncEnumerable<Tuple<PlexMovie, int>> GetMovieLibraryContents(
PlexLibrary library, PlexLibrary library,
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token) PlexServerAuthToken token)
@ -89,7 +89,7 @@ public class PlexServerApiClient : IPlexServerApiClient
return GetPagedLibraryContents(connection, CountItems, GetItems); return GetPagedLibraryContents(connection, CountItems, GetItems);
} }
public IAsyncEnumerable<PlexShow> GetShowLibraryContents( public IAsyncEnumerable<Tuple<PlexShow, int>> GetShowLibraryContents(
PlexLibrary library, PlexLibrary library,
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token) PlexServerAuthToken token)
@ -110,24 +110,7 @@ public class PlexServerApiClient : IPlexServerApiClient
return GetPagedLibraryContents(connection, CountItems, GetItems); return GetPagedLibraryContents(connection, CountItems, GetItems);
} }
public async Task<Either<BaseError, int>> CountShowSeasons( public IAsyncEnumerable<Tuple<PlexSeason, int>> GetShowSeasons(
PlexShow show,
PlexConnection connection,
PlexServerAuthToken token)
{
try
{
string showMetadataKey = show.Key.Split("/").Reverse().Skip(1).Head();
IPlexServerApi service = XmlServiceFor(connection.Uri);
return await service.CountShowChildren(showMetadataKey, token.AuthToken).Map(r => r.TotalSize);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
}
public IAsyncEnumerable<PlexSeason> GetShowSeasons(
PlexLibrary library, PlexLibrary library,
PlexShow show, PlexShow show,
PlexConnection connection, PlexConnection connection,
@ -150,24 +133,7 @@ public class PlexServerApiClient : IPlexServerApiClient
return GetPagedLibraryContents(connection, CountItems, GetItems); return GetPagedLibraryContents(connection, CountItems, GetItems);
} }
public async Task<Either<BaseError, int>> CountSeasonEpisodes( public IAsyncEnumerable<Tuple<PlexEpisode, int>> GetSeasonEpisodes(
PlexSeason season,
PlexConnection connection,
PlexServerAuthToken token)
{
try
{
string seasonMetadataKey = season.Key.Split("/").Reverse().Skip(1).Head();
IPlexServerApi service = XmlServiceFor(connection.Uri);
return await service.CountSeasonChildren(seasonMetadataKey, token.AuthToken).Map(r => r.TotalSize);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
}
public IAsyncEnumerable<PlexEpisode> GetSeasonEpisodes(
PlexLibrary library, PlexLibrary library,
PlexSeason season, PlexSeason season,
PlexConnection connection, PlexConnection connection,
@ -276,23 +242,7 @@ public class PlexServerApiClient : IPlexServerApiClient
} }
} }
public async Task<Either<BaseError, int>> GetLibraryItemCount( public IAsyncEnumerable<Tuple<PlexCollection, int>> GetAllCollections(
PlexLibrary library,
PlexConnection connection,
PlexServerAuthToken token)
{
try
{
IPlexServerApi service = XmlServiceFor(connection.Uri);
return await service.GetLibrarySection(library.Key, token.AuthToken).Map(r => r.TotalSize);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
}
public IAsyncEnumerable<PlexCollection> GetAllCollections(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@ -313,7 +263,7 @@ public class PlexServerApiClient : IPlexServerApiClient
} }
} }
public IAsyncEnumerable<MediaItem> GetCollectionItems( public IAsyncEnumerable<Tuple<MediaItem, int>> GetCollectionItems(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
string key, string key,
@ -335,7 +285,7 @@ public class PlexServerApiClient : IPlexServerApiClient
} }
} }
private static async IAsyncEnumerable<TItem> GetPagedLibraryContents<TItem>( private static async IAsyncEnumerable<Tuple<TItem, int>> GetPagedLibraryContents<TItem>(
PlexConnection connection, PlexConnection connection,
Func<IPlexServerApi, Task<PlexXmlMediaContainerStatsResponse>> countItems, Func<IPlexServerApi, Task<PlexXmlMediaContainerStatsResponse>> countItems,
Func<IPlexServerApi, IPlexServerApi, int, int, Task<IEnumerable<TItem>>> getItems) Func<IPlexServerApi, IPlexServerApi, int, int, Task<IEnumerable<TItem>>> getItems)
@ -357,7 +307,7 @@ public class PlexServerApiClient : IPlexServerApiClient
foreach (TItem item in await result) foreach (TItem item in await result)
{ {
yield return item; yield return new Tuple<TItem, int>(item, size);
} }
} }
} }

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

@ -30,7 +30,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
{ {
try try
{ {
// need to call get libraries to find library that contains collections (box sets) // need to call get libraries to find library that contains collections (box sets)
await _embyApiClient.GetLibraries(address, apiKey); await _embyApiClient.GetLibraries(address, apiKey);
var incomingItemIds = new List<string>(); var incomingItemIds = new List<string>();
@ -38,7 +38,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
// get all collections from db (item id, etag) // get all collections from db (item id, etag)
List<EmbyCollection> existingCollections = await _embyCollectionRepository.GetCollections(); List<EmbyCollection> existingCollections = await _embyCollectionRepository.GetCollections();
await foreach (EmbyCollection collection in _embyApiClient.GetCollectionLibraryItems(address, apiKey)) await foreach ((EmbyCollection collection, int _) in _embyApiClient.GetCollectionLibraryItems(address, apiKey))
{ {
incomingItemIds.Add(collection.ItemId); incomingItemIds.Add(collection.ItemId);
@ -88,13 +88,13 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
try try
{ {
// get collection items from Emby // get collection items from Emby
IAsyncEnumerable<MediaItem> items = _embyApiClient.GetCollectionItems(address, apiKey, collection.ItemId); IAsyncEnumerable<Tuple<MediaItem, int>> items = _embyApiClient.GetCollectionItems(address, apiKey, collection.ItemId);
List<int> removedIds = await _embyCollectionRepository.RemoveAllTags(collection); List<int> removedIds = await _embyCollectionRepository.RemoveAllTags(collection);
// sync tags on items // sync tags on items
var addedIds = new List<int>(); var addedIds = new List<int>();
await foreach (MediaItem item in items) await foreach ((MediaItem item, int _) in items)
{ {
addedIds.Add(await _embyCollectionRepository.AddTag(item, collection)); addedIds.Add(await _embyCollectionRepository.AddTag(item, collection));
} }

11
ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs

@ -75,16 +75,7 @@ public class EmbyMovieLibraryScanner :
protected override string MediaServerItemId(EmbyMovie movie) => movie.ItemId; protected override string MediaServerItemId(EmbyMovie movie) => movie.ItemId;
protected override string MediaServerEtag(EmbyMovie movie) => movie.Etag; protected override string MediaServerEtag(EmbyMovie movie) => movie.Etag;
protected override Task<Either<BaseError, int>> CountMovieLibraryItems( protected override IAsyncEnumerable<Tuple<EmbyMovie, int>> GetMovieLibraryItems(
EmbyConnectionParameters connectionParameters,
EmbyLibrary library) =>
_embyApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library.ItemId,
EmbyItemType.Movie);
protected override IAsyncEnumerable<EmbyMovie> GetMovieLibraryItems(
EmbyConnectionParameters connectionParameters, EmbyConnectionParameters connectionParameters,
EmbyLibrary library) => EmbyLibrary library) =>
_embyApiClient.GetMovieLibraryItems( _embyApiClient.GetMovieLibraryItems(

35
ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs

@ -72,16 +72,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
cancellationToken); cancellationToken);
} }
protected override Task<Either<BaseError, int>> CountShowLibraryItems( protected override IAsyncEnumerable<Tuple<EmbyShow, int>> GetShowLibraryItems(
EmbyConnectionParameters connectionParameters,
EmbyLibrary library)
=> _embyApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library.ItemId,
EmbyItemType.Show);
protected override IAsyncEnumerable<EmbyShow> GetShowLibraryItems(
EmbyConnectionParameters connectionParameters, EmbyConnectionParameters connectionParameters,
EmbyLibrary library) => EmbyLibrary library) =>
_embyApiClient.GetShowLibraryItems(connectionParameters.Address, connectionParameters.ApiKey, library); _embyApiClient.GetShowLibraryItems(connectionParameters.Address, connectionParameters.ApiKey, library);
@ -94,17 +85,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
protected override string MediaServerEtag(EmbySeason season) => season.Etag; protected override string MediaServerEtag(EmbySeason season) => season.Etag;
protected override string MediaServerEtag(EmbyEpisode episode) => episode.Etag; protected override string MediaServerEtag(EmbyEpisode episode) => episode.Etag;
protected override Task<Either<BaseError, int>> CountSeasonLibraryItems( protected override IAsyncEnumerable<Tuple<EmbySeason, int>> GetSeasonLibraryItems(
EmbyConnectionParameters connectionParameters,
EmbyLibrary library,
EmbyShow show) =>
_embyApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
show.ItemId,
EmbyItemType.Season);
protected override IAsyncEnumerable<EmbySeason> GetSeasonLibraryItems(
EmbyLibrary library, EmbyLibrary library,
EmbyConnectionParameters connectionParameters, EmbyConnectionParameters connectionParameters,
EmbyShow show) => EmbyShow show) =>
@ -114,17 +95,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
library, library,
show.ItemId); show.ItemId);
protected override Task<Either<BaseError, int>> CountEpisodeLibraryItems( protected override IAsyncEnumerable<Tuple<EmbyEpisode, int>> GetEpisodeLibraryItems(
EmbyConnectionParameters connectionParameters,
EmbyLibrary library,
EmbySeason season) =>
_embyApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
season.ItemId,
EmbyItemType.Episode);
protected override IAsyncEnumerable<EmbyEpisode> GetEpisodeLibraryItems(
EmbyLibrary library, EmbyLibrary library,
EmbyConnectionParameters connectionParameters, EmbyConnectionParameters connectionParameters,
EmbyShow show, EmbyShow show,

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

@ -42,7 +42,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
return error; return error;
} }
// need to call get libraries to find library that contains collections (box sets) // need to call get libraries to find library that contains collections (box sets)
await _jellyfinApiClient.GetLibraries(address, apiKey); await _jellyfinApiClient.GetLibraries(address, apiKey);
var incomingItemIds = new List<string>(); var incomingItemIds = new List<string>();
@ -51,7 +51,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
List<JellyfinCollection> existingCollections = await _jellyfinCollectionRepository.GetCollections(); List<JellyfinCollection> existingCollections = await _jellyfinCollectionRepository.GetCollections();
// loop over collections // loop over collections
await foreach (JellyfinCollection collection in _jellyfinApiClient.GetCollectionLibraryItems( await foreach ((JellyfinCollection collection, int _) in _jellyfinApiClient.GetCollectionLibraryItems(
address, address,
apiKey, apiKey,
mediaSourceId)) mediaSourceId))
@ -105,7 +105,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
try try
{ {
// get collection items from JF // get collection items from JF
IAsyncEnumerable<MediaItem> items = _jellyfinApiClient.GetCollectionItems( IAsyncEnumerable<Tuple<MediaItem, int>> items = _jellyfinApiClient.GetCollectionItems(
address, address,
apiKey, apiKey,
mediaSourceId, mediaSourceId,
@ -115,7 +115,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
// sync tags on items // sync tags on items
var addedIds = new List<int>(); var addedIds = new List<int>();
await foreach (MediaItem item in items) await foreach ((MediaItem item, int _) in items)
{ {
addedIds.Add(await _jellyfinCollectionRepository.AddTag(item, collection)); addedIds.Add(await _jellyfinCollectionRepository.AddTag(item, collection));
} }

13
ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs

@ -76,18 +76,7 @@ public class JellyfinMovieLibraryScanner :
protected override string MediaServerEtag(JellyfinMovie movie) => movie.Etag; protected override string MediaServerEtag(JellyfinMovie movie) => movie.Etag;
protected override Task<Either<BaseError, int>> CountMovieLibraryItems( protected override IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(
JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library) =>
_jellyfinApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library,
library.ItemId,
JellyfinItemType.Movie,
true);
protected override IAsyncEnumerable<JellyfinMovie> GetMovieLibraryItems(
JellyfinConnectionParameters connectionParameters, JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library) => JellyfinLibrary library) =>
_jellyfinApiClient.GetMovieLibraryItems( _jellyfinApiClient.GetMovieLibraryItems(

41
ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs

@ -73,18 +73,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
cancellationToken); cancellationToken);
} }
protected override Task<Either<BaseError, int>> CountShowLibraryItems( protected override IAsyncEnumerable<Tuple<JellyfinShow, int>> GetShowLibraryItems(
JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library)
=> _jellyfinApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library,
library.ItemId,
JellyfinItemType.Show,
false);
protected override IAsyncEnumerable<JellyfinShow> GetShowLibraryItems(
JellyfinConnectionParameters connectionParameters, JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library) => JellyfinLibrary library) =>
_jellyfinApiClient.GetShowLibraryItems(connectionParameters.Address, connectionParameters.ApiKey, library); _jellyfinApiClient.GetShowLibraryItems(connectionParameters.Address, connectionParameters.ApiKey, library);
@ -97,19 +86,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
protected override string MediaServerEtag(JellyfinSeason season) => season.Etag; protected override string MediaServerEtag(JellyfinSeason season) => season.Etag;
protected override string MediaServerEtag(JellyfinEpisode episode) => episode.Etag; protected override string MediaServerEtag(JellyfinEpisode episode) => episode.Etag;
protected override Task<Either<BaseError, int>> CountSeasonLibraryItems( protected override IAsyncEnumerable<Tuple<JellyfinSeason, int>> GetSeasonLibraryItems(
JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library,
JellyfinShow show) =>
_jellyfinApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library,
show.ItemId,
JellyfinItemType.Season,
false);
protected override IAsyncEnumerable<JellyfinSeason> GetSeasonLibraryItems(
JellyfinLibrary library, JellyfinLibrary library,
JellyfinConnectionParameters connectionParameters, JellyfinConnectionParameters connectionParameters,
JellyfinShow show) => JellyfinShow show) =>
@ -119,19 +96,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
library, library,
show.ItemId); show.ItemId);
protected override Task<Either<BaseError, int>> CountEpisodeLibraryItems( protected override IAsyncEnumerable<Tuple<JellyfinEpisode, int>> GetEpisodeLibraryItems(
JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library,
JellyfinSeason season) =>
_jellyfinApiClient.GetLibraryItemCount(
connectionParameters.Address,
connectionParameters.ApiKey,
library,
season.ItemId,
JellyfinItemType.Episode,
true);
protected override IAsyncEnumerable<JellyfinEpisode> GetEpisodeLibraryItems(
JellyfinLibrary library, JellyfinLibrary library,
JellyfinConnectionParameters connectionParameters, JellyfinConnectionParameters connectionParameters,
JellyfinShow show, JellyfinShow show,

40
ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -48,27 +48,14 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
{ {
try try
{ {
Either<BaseError, int> maybeCount = await CountMovieLibraryItems(connectionParameters, library); return await ScanLibrary(
foreach (BaseError error in maybeCount.LeftToSeq()) movieRepository,
{ connectionParameters,
return error; library,
} getLocalPath,
GetMovieLibraryItems(connectionParameters, library),
foreach (int count in maybeCount.RightToSeq()) deepScan,
{ cancellationToken);
return await ScanLibrary(
movieRepository,
connectionParameters,
library,
getLocalPath,
GetMovieLibraryItems(connectionParameters, library),
count,
deepScan,
cancellationToken);
}
// this won't happen
return Unit.Default;
} }
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{ {
@ -81,8 +68,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TLibrary library, TLibrary library,
Func<TMovie, string> getLocalPath, Func<TMovie, string> getLocalPath,
IAsyncEnumerable<TMovie> movieEntries, IAsyncEnumerable<Tuple<TMovie, int>> movieEntries,
int totalMovieCount,
bool deepScan, bool deepScan,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@ -90,7 +76,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
IReadOnlyDictionary<string, TEtag> existingMovies = (await movieRepository.GetExistingMovies(library)) IReadOnlyDictionary<string, TEtag> existingMovies = (await movieRepository.GetExistingMovies(library))
.ToImmutableDictionary(e => e.MediaServerItemId, e => e); .ToImmutableDictionary(e => e.MediaServerItemId, e => e);
await foreach (TMovie incoming in movieEntries.WithCancellation(cancellationToken)) await foreach ((TMovie incoming, int totalMovieCount) in movieEntries.WithCancellation(cancellationToken))
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -237,11 +223,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
protected abstract string MediaServerItemId(TMovie movie); protected abstract string MediaServerItemId(TMovie movie);
protected abstract string MediaServerEtag(TMovie movie); protected abstract string MediaServerEtag(TMovie movie);
protected abstract Task<Either<BaseError, int>> CountMovieLibraryItems( protected abstract IAsyncEnumerable<Tuple<TMovie, int>> GetMovieLibraryItems(
TConnectionParameters connectionParameters,
TLibrary library);
protected abstract IAsyncEnumerable<TMovie> GetMovieLibraryItems(
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TLibrary library); TLibrary library);

99
ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs

@ -50,29 +50,14 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
{ {
try try
{ {
Either<BaseError, int> maybeCount = await CountShowLibraryItems(connectionParameters, library); return await ScanLibrary(
foreach (BaseError error in maybeCount.LeftToSeq()) televisionRepository,
{ connectionParameters,
return error; library,
} getLocalPath,
GetShowLibraryItems(connectionParameters, library),
foreach (int count in maybeCount.RightToSeq()) deepScan,
{ cancellationToken);
_logger.LogDebug("Library {Library} contains {Count} shows", library.Name, count);
return await ScanLibrary(
televisionRepository,
connectionParameters,
library,
getLocalPath,
GetShowLibraryItems(connectionParameters, library),
count,
deepScan,
cancellationToken);
}
// this won't happen
return Unit.Default;
} }
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{ {
@ -80,11 +65,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
} }
} }
protected abstract Task<Either<BaseError, int>> CountShowLibraryItems( protected abstract IAsyncEnumerable<Tuple<TShow, int>> GetShowLibraryItems(
TConnectionParameters connectionParameters,
TLibrary library);
protected abstract IAsyncEnumerable<TShow> GetShowLibraryItems(
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TLibrary library); TLibrary library);
@ -100,15 +81,14 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TLibrary library, TLibrary library,
Func<TEpisode, string> getLocalPath, Func<TEpisode, string> getLocalPath,
IAsyncEnumerable<TShow> showEntries, IAsyncEnumerable<Tuple<TShow, int>> showEntries,
int totalShowCount,
bool deepScan, bool deepScan,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var incomingItemIds = new List<string>(); var incomingItemIds = new List<string>();
List<TEtag> existingShows = await televisionRepository.GetExistingShows(library); List<TEtag> existingShows = await televisionRepository.GetExistingShows(library);
await foreach (TShow incoming in showEntries.WithCancellation(cancellationToken)) await foreach ((TShow incoming, int totalShowCount) in showEntries.WithCancellation(cancellationToken))
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -146,23 +126,6 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
foreach (MediaItemScanResult<TShow> result in maybeShow.RightToSeq()) foreach (MediaItemScanResult<TShow> result in maybeShow.RightToSeq())
{ {
Either<BaseError, int> maybeCount = await CountSeasonLibraryItems(
connectionParameters,
library,
result.Item);
foreach (BaseError error in maybeCount.LeftToSeq())
{
return error;
}
foreach (int count in maybeCount.RightToSeq())
{
_logger.LogDebug(
"Show {Title} contains {Count} seasons",
result.Item.ShowMetadata.Head().Title,
count);
}
Either<BaseError, Unit> scanResult = await ScanSeasons( Either<BaseError, Unit> scanResult = await ScanSeasons(
televisionRepository, televisionRepository,
library, library,
@ -220,22 +183,12 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
return Unit.Default; return Unit.Default;
} }
protected abstract Task<Either<BaseError, int>> CountSeasonLibraryItems( protected abstract IAsyncEnumerable<Tuple<TSeason, int>> GetSeasonLibraryItems(
TConnectionParameters connectionParameters,
TLibrary library,
TShow show);
protected abstract IAsyncEnumerable<TSeason> GetSeasonLibraryItems(
TLibrary library, TLibrary library,
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TShow show); TShow show);
protected abstract Task<Either<BaseError, int>> CountEpisodeLibraryItems( protected abstract IAsyncEnumerable<Tuple<TEpisode, int>> GetEpisodeLibraryItems(
TConnectionParameters connectionParameters,
TLibrary library,
TSeason season);
protected abstract IAsyncEnumerable<TEpisode> GetEpisodeLibraryItems(
TLibrary library, TLibrary library,
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
TShow show, TShow show,
@ -293,14 +246,14 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
TShow show, TShow show,
bool showIsUpdated, bool showIsUpdated,
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
IAsyncEnumerable<TSeason> seasonEntries, IAsyncEnumerable<Tuple<TSeason, int>> seasonEntries,
bool deepScan, bool deepScan,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var incomingItemIds = new List<string>(); var incomingItemIds = new List<string>();
List<TEtag> existingSeasons = await televisionRepository.GetExistingSeasons(library, show); List<TEtag> existingSeasons = await televisionRepository.GetExistingSeasons(library, show);
await foreach (TSeason incoming in seasonEntries.WithCancellation(cancellationToken)) await foreach ((TSeason incoming, int _) in seasonEntries.WithCancellation(cancellationToken))
{ {
incoming.ShowId = show.Id; incoming.ShowId = show.Id;
@ -331,24 +284,6 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
foreach (MediaItemScanResult<TSeason> result in maybeSeason.RightToSeq()) foreach (MediaItemScanResult<TSeason> result in maybeSeason.RightToSeq())
{ {
Either<BaseError, int> maybeCount = await CountEpisodeLibraryItems(
connectionParameters,
library,
result.Item);
foreach (BaseError error in maybeCount.LeftToSeq())
{
return error;
}
foreach (int count in maybeCount.RightToSeq())
{
_logger.LogDebug(
"Show {Title} season {Season} contains {Count} episodes",
show.ShowMetadata.Head().Title,
result.Item.SeasonNumber,
count);
}
Either<BaseError, Unit> scanResult = await ScanEpisodes( Either<BaseError, Unit> scanResult = await ScanEpisodes(
televisionRepository, televisionRepository,
library, library,
@ -408,14 +343,14 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
bool showIsUpdated, bool showIsUpdated,
TSeason season, TSeason season,
TConnectionParameters connectionParameters, TConnectionParameters connectionParameters,
IAsyncEnumerable<TEpisode> episodeEntries, IAsyncEnumerable<Tuple<TEpisode, int>> episodeEntries,
bool deepScan, bool deepScan,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var incomingItemIds = new List<string>(); var incomingItemIds = new List<string>();
List<TEtag> existingEpisodes = await televisionRepository.GetExistingEpisodes(library, season); List<TEtag> existingEpisodes = await televisionRepository.GetExistingEpisodes(library, season);
await foreach (TEpisode incoming in episodeEntries.WithCancellation(cancellationToken)) await foreach ((TEpisode incoming, int _) in episodeEntries.WithCancellation(cancellationToken))
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {

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

@ -39,7 +39,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner
// get all collections from db (key, etag) // get all collections from db (key, etag)
List<PlexCollection> existingCollections = await _plexCollectionRepository.GetCollections(); List<PlexCollection> existingCollections = await _plexCollectionRepository.GetCollections();
await foreach (PlexCollection collection in _plexServerApiClient.GetAllCollections( await foreach ((PlexCollection collection, int _) in _plexServerApiClient.GetAllCollections(
connection, connection,
token, token,
cancellationToken)) cancellationToken))
@ -93,7 +93,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner
try try
{ {
// get collection items from Plex // get collection items from Plex
IAsyncEnumerable<MediaItem> items = _plexServerApiClient.GetCollectionItems( IAsyncEnumerable<Tuple<MediaItem, int>> items = _plexServerApiClient.GetCollectionItems(
connection, connection,
token, token,
collection.Key, collection.Key,
@ -103,7 +103,7 @@ public class PlexCollectionScanner : IPlexCollectionScanner
// sync tags on items // sync tags on items
var addedIds = new List<int>(); var addedIds = new List<int>();
await foreach (MediaItem item in items) await foreach ((MediaItem item, int _) in items)
{ {
addedIds.Add(await _plexCollectionRepository.AddTag(item, collection)); addedIds.Add(await _plexCollectionRepository.AddTag(item, collection));
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();

10
ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs

@ -82,15 +82,7 @@ public class PlexMovieLibraryScanner :
protected override string MediaServerEtag(PlexMovie movie) => movie.Etag; protected override string MediaServerEtag(PlexMovie movie) => movie.Etag;
protected override Task<Either<BaseError, int>> CountMovieLibraryItems( protected override IAsyncEnumerable<Tuple<PlexMovie, int>> GetMovieLibraryItems(
PlexConnectionParameters connectionParameters,
PlexLibrary library)
=> _plexServerApiClient.GetLibraryItemCount(
library,
connectionParameters.Connection,
connectionParameters.Token);
protected override IAsyncEnumerable<PlexMovie> GetMovieLibraryItems(
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexLibrary library) => PlexLibrary library) =>
_plexServerApiClient.GetMovieLibraryContents( _plexServerApiClient.GetMovieLibraryContents(

32
ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs

@ -130,15 +130,7 @@ public class PlexTelevisionLibraryScanner :
// } // }
// } // }
protected override Task<Either<BaseError, int>> CountShowLibraryItems( protected override IAsyncEnumerable<Tuple<PlexShow, int>> GetShowLibraryItems(
PlexConnectionParameters connectionParameters,
PlexLibrary library) =>
_plexServerApiClient.GetLibraryItemCount(
library,
connectionParameters.Connection,
connectionParameters.Token);
protected override IAsyncEnumerable<PlexShow> GetShowLibraryItems(
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexLibrary library) => PlexLibrary library) =>
_plexServerApiClient.GetShowLibraryContents( _plexServerApiClient.GetShowLibraryContents(
@ -146,16 +138,7 @@ public class PlexTelevisionLibraryScanner :
connectionParameters.Connection, connectionParameters.Connection,
connectionParameters.Token); connectionParameters.Token);
protected override Task<Either<BaseError, int>> CountSeasonLibraryItems( protected override IAsyncEnumerable<Tuple<PlexSeason, int>> GetSeasonLibraryItems(
PlexConnectionParameters connectionParameters,
PlexLibrary library,
PlexShow show) =>
_plexServerApiClient.CountShowSeasons(
show,
connectionParameters.Connection,
connectionParameters.Token);
protected override IAsyncEnumerable<PlexSeason> GetSeasonLibraryItems(
PlexLibrary library, PlexLibrary library,
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexShow show) => PlexShow show) =>
@ -165,16 +148,7 @@ public class PlexTelevisionLibraryScanner :
connectionParameters.Connection, connectionParameters.Connection,
connectionParameters.Token); connectionParameters.Token);
protected override Task<Either<BaseError, int>> CountEpisodeLibraryItems( protected override IAsyncEnumerable<Tuple<PlexEpisode, int>> GetEpisodeLibraryItems(
PlexConnectionParameters connectionParameters,
PlexLibrary library,
PlexSeason season) =>
_plexServerApiClient.CountSeasonEpisodes(
season,
connectionParameters.Connection,
connectionParameters.Token);
protected override IAsyncEnumerable<PlexEpisode> GetEpisodeLibraryItems(
PlexLibrary library, PlexLibrary library,
PlexConnectionParameters connectionParameters, PlexConnectionParameters connectionParameters,
PlexShow show, PlexShow show,

Loading…
Cancel
Save