fix(jellyfin): include movie home hubs

This commit is contained in:
edde746
2026-05-10 00:53:20 +02:00
parent dd0fd1c828
commit 3a93ec80ec
2 changed files with 60 additions and 8 deletions
+12 -8
View File
@@ -87,10 +87,10 @@ class DataAggregationService {
}
/// Fetch recommendation hubs from all servers as neutral [MediaHub]s.
/// When useGlobalHubs is true (default), uses the global /hubs endpoint
/// to get the true home page hubs like "Recently Added Movies", "Recently
/// Added TV"; when false, uses per-library hubs from
/// /hubs/sections/{sectionId}.
/// When useGlobalHubs is true (default), rich-hub backends use the global
/// /hubs endpoint to get true home page hubs like "Recently Added Movies".
/// Backends without rich home hubs fall back to per-library hubs so one
/// capped "Latest" response cannot hide whole library types.
Future<List<MediaHub>> getHubsFromAllServers({
int? limit,
Set<String>? hiddenLibraryKeys,
@@ -111,20 +111,23 @@ class DataAggregationService {
final serverId = entry.key;
final client = entry.value;
try {
final hubs = useGlobalHubs
final serverLibraries = libraries?[serverId];
final shouldUseGlobalHubs = useGlobalHubs && client.capabilities.richHubs;
final hubs = shouldUseGlobalHubs
? await client.fetchGlobalHubs(limit: limit ?? 10, includePlaybackHubs: includePlaybackHubs)
: await _fetchLibraryHubsForClient(
client,
limit: limit ?? 10,
hiddenLibraryKeys: hiddenLibraryKeys,
includePlaybackHubs: includePlaybackHubs,
libraries: useGlobalHubs ? serverLibraries : null,
);
return _postProcessHubs(
hubs,
serverId: serverId,
hiddenLibraryKeys: hiddenLibraryKeys,
libraries: libraries?[serverId],
splitRecentlyAdded: useGlobalHubs,
libraries: serverLibraries,
splitRecentlyAdded: shouldUseGlobalHubs,
);
} catch (e, stackTrace) {
appLogger.e('Failed to fetch hubs from server $serverId', error: e, stackTrace: stackTrace);
@@ -148,8 +151,9 @@ class DataAggregationService {
required int limit,
Set<String>? hiddenLibraryKeys,
required bool includePlaybackHubs,
List<MediaLibrary>? libraries,
}) async {
final libs = await client.fetchLibraries();
final libs = libraries ?? await client.fetchLibraries();
final visible = libs.where((l) {
if (l.kind != MediaKind.movie && l.kind != MediaKind.show) return false;
if (l.hidden) return false;
@@ -111,5 +111,53 @@ void main() {
['lib-1', 'lib-2', 'lib-3', 'lib-4'],
);
});
test('global home layout falls back to per-library hubs for Jellyfin', () async {
final captured = <Uri>[];
final client = JellyfinClient.forTesting(
connection: _conn(),
httpClient: MockClient((req) async {
captured.add(req.url);
if (req.url.path == '/Users/user-1/Views') {
return _json({
'Items': [
{'Id': 'movies', 'Name': 'Movies', 'CollectionType': 'movies'},
{'Id': 'shows', 'Name': 'Shows', 'CollectionType': 'tvshows'},
],
});
}
if (req.url.path == '/Users/user-1/Items/Latest') {
final parentId = req.url.queryParameters['ParentId'];
return switch (parentId) {
'movies' => _json({
'Items': [
{'Id': 'movie-1', 'Type': 'Movie', 'Name': 'Latest Movie', 'ParentLibraryId': 'movies'},
],
}),
'shows' => _json({
'Items': [
{'Id': 'show-1', 'Type': 'Series', 'Name': 'Latest Show', 'ParentLibraryId': 'shows'},
],
}),
_ => http.Response('mixed latest should not be requested', 500),
};
}
return http.Response('unexpected request', 500);
}),
);
addTearDown(client.close);
manager.debugRegisterJellyfinClientForTesting(client);
final hubs = await service.getHubsFromAllServers(useGlobalHubs: true, includePlaybackHubs: false);
expect(hubs.map((h) => h.identifier), ['library.movies.recent', 'library.shows.recent']);
expect(hubs.map((h) => h.items.single.id), ['movie-1', 'show-1']);
expect(captured.where((uri) => uri.path == '/Users/user-1/Views'), hasLength(1));
expect(
captured.where((uri) => uri.path == '/Users/user-1/Items/Latest').map((uri) => uri.queryParameters['ParentId']),
['movies', 'shows'],
);
});
});
}