fix: split combined recently added hubs by library

closes #552
This commit is contained in:
edde746
2026-02-26 00:30:23 +01:00
parent 3279bc7578
commit acf91107ad
3 changed files with 113 additions and 2 deletions
+4
View File
@@ -92,6 +92,7 @@ class PlexMetadata with MultiServerFields {
final int? playlistItemID; // Playlist item ID (for dumb playlists only)
final int? playQueueItemID; // Play queue item ID (unique even for duplicates)
final int? librarySectionID; // Library section ID this item belongs to
final String? librarySectionTitle; // Library section title this item belongs to
final String? ratingImage; // Rating source URI (e.g. rottentomatoes://image.rating.ripe)
final String? audienceRatingImage; // Audience rating source URI
final String? tagline;
@@ -174,6 +175,7 @@ class PlexMetadata with MultiServerFields {
this.playlistItemID,
this.playQueueItemID,
this.librarySectionID,
this.librarySectionTitle,
this.ratingImage,
this.audienceRatingImage,
this.tagline,
@@ -229,6 +231,7 @@ class PlexMetadata with MultiServerFields {
int? playlistItemID,
int? playQueueItemID,
int? librarySectionID,
String? librarySectionTitle,
String? ratingImage,
String? audienceRatingImage,
String? tagline,
@@ -282,6 +285,7 @@ class PlexMetadata with MultiServerFields {
playlistItemID: playlistItemID ?? this.playlistItemID,
playQueueItemID: playQueueItemID ?? this.playQueueItemID,
librarySectionID: librarySectionID ?? this.librarySectionID,
librarySectionTitle: librarySectionTitle ?? this.librarySectionTitle,
ratingImage: ratingImage ?? this.ratingImage,
audienceRatingImage: audienceRatingImage ?? this.audienceRatingImage,
tagline: tagline ?? this.tagline,
+2
View File
@@ -48,6 +48,7 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
playlistItemID: (json['playlistItemID'] as num?)?.toInt(),
playQueueItemID: (json['playQueueItemID'] as num?)?.toInt(),
librarySectionID: (json['librarySectionID'] as num?)?.toInt(),
librarySectionTitle: json['librarySectionTitle'] as String?,
ratingImage: json['ratingImage'] as String?,
audienceRatingImage: json['audienceRatingImage'] as String?,
tagline: json['tagline'] as String?,
@@ -100,6 +101,7 @@ Map<String, dynamic> _$PlexMetadataToJson(PlexMetadata instance) => <String, dyn
'playlistItemID': instance.playlistItemID,
'playQueueItemID': instance.playQueueItemID,
'librarySectionID': instance.librarySectionID,
'librarySectionTitle': instance.librarySectionTitle,
'ratingImage': instance.ratingImage,
'audienceRatingImage': instance.audienceRatingImage,
'tagline': instance.tagline,
+107 -2
View File
@@ -84,8 +84,13 @@ class DataAggregationService {
return [];
}
// For global hubs, fetch libraries to split "Recently Added" hubs by library
final libraries = useGlobalHubs
? (librariesByServer ?? groupLibrariesByServer(await getLibrariesFromAllServers()))
: librariesByServer;
return useGlobalHubs
? _fetchGlobalHubs(clients, limit: limit, hiddenLibraryKeys: hiddenLibraryKeys)
? _fetchGlobalHubs(clients, limit: limit, hiddenLibraryKeys: hiddenLibraryKeys, librariesByServer: libraries)
: _fetchLibraryHubs(
clients,
limit: limit,
@@ -99,6 +104,7 @@ class DataAggregationService {
Map<String, PlexClient> clients, {
int? limit,
Set<String>? hiddenLibraryKeys,
Map<String, List<PlexLibrary>>? librariesByServer,
}) async {
appLogger.d('Fetching global hubs from ${clients.length} servers');
@@ -150,7 +156,9 @@ class DataAggregationService {
});
final results = await Future.wait(hubFutures);
final result = _collectAndLimitResults(results, limit);
// Split "Recently Added" hubs that combine items from multiple libraries
final splitResults = results.map((hubs) => _splitRecentlyAddedHubs(hubs, librariesByServer)).toList();
final result = _collectAndLimitResults(splitResults, limit);
appLogger.i('Fetched ${result.length} global hubs from all servers');
@@ -300,6 +308,103 @@ class DataAggregationService {
return limit != null && limit < all.length ? all.sublist(0, limit) : all;
}
/// Split "Recently Added" hubs that contain items from multiple libraries
/// into separate per-library hubs, matching the official Plex client behavior.
List<PlexHub> _splitRecentlyAddedHubs(
List<PlexHub> hubs,
Map<String, List<PlexLibrary>>? librariesByServer,
) {
final result = <PlexHub>[];
for (final hub in hubs) {
final hubId = hub.hubIdentifier?.toLowerCase() ?? '';
if (!hubId.contains('.recent')) {
result.add(hub);
continue;
}
// Group items by librarySectionID
final groups = <int, List<PlexMetadata>>{};
final ungrouped = <PlexMetadata>[];
for (final item in hub.items) {
final sectionId = item.librarySectionID;
if (sectionId == null) {
ungrouped.add(item);
} else {
groups.putIfAbsent(sectionId, () => []).add(item);
}
}
// Single library (or no groupable items) — keep hub unchanged
if (groups.length <= 1) {
result.add(hub);
continue;
}
// Multiple libraries — create one hub per library
for (final entry in groups.entries) {
final items = entry.value;
final libraryName = _resolveLibraryName(items.first, librariesByServer);
final title = libraryName != null ? 'Recently Added in $libraryName' : hub.title;
result.add(PlexHub(
hubKey: hub.hubKey,
title: title,
type: hub.type,
hubIdentifier: '${hub.hubIdentifier}_${entry.key}',
size: items.length,
more: hub.more,
items: items,
serverId: hub.serverId,
serverName: hub.serverName,
));
}
// Keep ungrouped items in a hub with the original title
if (ungrouped.isNotEmpty) {
result.add(PlexHub(
hubKey: hub.hubKey,
title: hub.title,
type: hub.type,
hubIdentifier: hub.hubIdentifier,
size: ungrouped.length,
more: hub.more,
items: ungrouped,
serverId: hub.serverId,
serverName: hub.serverName,
));
}
}
return result;
}
/// Resolve library name from item metadata or library lookup map.
String? _resolveLibraryName(
PlexMetadata item,
Map<String, List<PlexLibrary>>? librariesByServer,
) {
// Try librarySectionTitle from the item itself (Plex API often includes it)
if (item.librarySectionTitle != null && item.librarySectionTitle!.isNotEmpty) {
return item.librarySectionTitle;
}
// Fall back to library lookup
if (librariesByServer != null && item.serverId != null && item.librarySectionID != null) {
final serverLibraries = librariesByServer[item.serverId];
if (serverLibraries != null) {
for (final lib in serverLibraries) {
if (lib.key == item.librarySectionID.toString()) {
return lib.title;
}
}
}
}
return null;
}
/// Base helper for per-server fan-out operations
///
/// Returns raw results as (serverId, result) tuples.