fix(libraries): filter content-type-less Jellyfin roots

This commit is contained in:
edde746
2026-07-25 17:38:09 +02:00
parent c1cfb9609e
commit 96fd47a160
4 changed files with 30 additions and 25 deletions
@@ -767,7 +767,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
limit: size, limit: size,
); );
final query = final query =
baseQuery.kind == null && (baseQuery.kind == null || baseQuery.kind == MediaKind.folder) &&
baseQuery.includeKinds.isEmpty && baseQuery.includeKinds.isEmpty &&
_selectedGrouping == browseGroupingAll && _selectedGrouping == browseGroupingAll &&
widget.library.defaultBrowseKinds.isNotEmpty widget.library.defaultBrowseKinds.isNotEmpty
+10 -7
View File
@@ -268,12 +268,13 @@ class JellyfinMappers {
final id = view['Id'] as String?; final id = view['Id'] as String?;
if (id == null || id.isEmpty) return null; if (id == null || id.isEmpty) return null;
final collectionType = view['CollectionType'] as String?; final collectionType = view['CollectionType'] as String?;
final type = view['Type'] as String?;
return MediaLibrary( return MediaLibrary(
id: id, id: id,
backend: MediaBackend.jellyfin, backend: MediaBackend.jellyfin,
title: view['Name'] as String? ?? t.libraries.fallbackTitle, title: view['Name'] as String? ?? t.libraries.fallbackTitle,
kind: _libraryKindFromCollectionType(collectionType, view['Type'] as String?), kind: _libraryKindFromCollectionType(collectionType, type),
defaultBrowseKinds: _defaultBrowseKindsFromCollectionType(collectionType), defaultBrowseKinds: _defaultBrowseKindsFromCollectionType(collectionType, type),
updatedAt: jellyfinIsoToEpochSeconds(view['DateLastSaved'] as String? ?? view['DateModified'] as String?), updatedAt: jellyfinIsoToEpochSeconds(view['DateLastSaved'] as String? ?? view['DateModified'] as String?),
createdAt: jellyfinIsoToEpochSeconds(view['DateCreated'] as String?), createdAt: jellyfinIsoToEpochSeconds(view['DateCreated'] as String?),
hidden: false, hidden: false,
@@ -317,8 +318,8 @@ class JellyfinMappers {
} }
static MediaKind _libraryKindFromCollectionType(String? collectionType, String? type) { static MediaKind _libraryKindFromCollectionType(String? collectionType, String? type) {
final ct = collectionType?.toLowerCase(); final ct = collectionType?.trim().toLowerCase();
if (ct != null) { if (ct != null && ct.isNotEmpty) {
return switch (ct) { return switch (ct) {
'movies' => MediaKind.movie, 'movies' => MediaKind.movie,
'tvshows' => MediaKind.show, 'tvshows' => MediaKind.show,
@@ -328,15 +329,17 @@ class JellyfinMappers {
'photos' => MediaKind.photo, 'photos' => MediaKind.photo,
'boxsets' => MediaKind.collection, 'boxsets' => MediaKind.collection,
'playlists' => MediaKind.playlist, 'playlists' => MediaKind.playlist,
'mixed' => MediaKind.unknown,
_ => MediaKind.unknown, _ => MediaKind.unknown,
}; };
} }
return MediaKind.fromString(type); return MediaKind.fromString(type);
} }
static List<MediaKind> _defaultBrowseKindsFromCollectionType(String? collectionType) { static List<MediaKind> _defaultBrowseKindsFromCollectionType(String? collectionType, String? type) {
return collectionType?.toLowerCase() == 'mixed' ? const [MediaKind.movie, MediaKind.show] : const <MediaKind>[]; final ct = collectionType?.trim();
return (ct == null || ct.isEmpty) && type?.toLowerCase() == 'collectionfolder'
? const [MediaKind.movie, MediaKind.show]
: const <MediaKind>[];
} }
static Map<String, dynamic>? _userData(Map<String, dynamic> item) { static Map<String, dynamic>? _userData(Map<String, dynamic> item) {
@@ -20,6 +20,7 @@ import 'package:plezy/providers/multi_server_provider.dart';
import 'package:plezy/screens/libraries/state_messages.dart'; import 'package:plezy/screens/libraries/state_messages.dart';
import 'package:plezy/screens/libraries/tabs/library_browse_tab.dart'; import 'package:plezy/screens/libraries/tabs/library_browse_tab.dart';
import 'package:plezy/services/data_aggregation_service.dart'; import 'package:plezy/services/data_aggregation_service.dart';
import 'package:plezy/services/jellyfin_mappers.dart';
import 'package:plezy/services/multi_server_manager.dart'; import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/settings_service.dart'; import 'package:plezy/services/settings_service.dart';
import 'package:plezy/services/storage_service.dart'; import 'package:plezy/services/storage_service.dart';
@@ -137,20 +138,19 @@ void main() {
final client = _BrowseClient('server-a', 'Mixed'); final client = _BrowseClient('server-a', 'Mixed');
final harness = _BrowseHarness(clientA: client); final harness = _BrowseHarness(clientA: client);
addTearDown(harness.dispose); addTearDown(harness.dispose);
harness.selectedLibrary.value = MediaLibrary( harness.selectedLibrary.value = JellyfinMappers.library({
id: 'mixed-library', 'Id': 'mixed-library',
backend: MediaBackend.jellyfin, 'Name': 'Mixed',
title: 'Mixed', 'Type': 'CollectionFolder',
defaultBrowseKinds: const [MediaKind.movie, MediaKind.show], 'IsFolder': true,
serverId: client.serverId, }, serverId: client.serverId)!;
);
await _pumpHarness(tester, harness); await _pumpHarness(tester, harness);
expect(client.pageQueries, hasLength(1)); expect(client.pageQueries, hasLength(1));
expect(client.pageQueries.single.kind, isNull); expect(client.pageQueries.single.kind, MediaKind.folder);
expect(client.pageQueries.single.includeKinds, const [MediaKind.movie, MediaKind.show]); expect(client.pageQueries.single.includeKinds, const [MediaKind.movie, MediaKind.show]);
expect(client.pageLibraryKinds.single, MediaKind.unknown); expect(client.pageLibraryKinds.single, MediaKind.folder);
}); });
} }
+10 -8
View File
@@ -474,18 +474,20 @@ void main() {
} }
}); });
test('maps mixed collections to a movie and show root browse', () { test('maps content-type-less collection folders to a movie and show root browse', () {
final mixed = JellyfinMappers.library({ for (final view in [
'Id': 'view-mixed', {'Id': 'view-missing-type', 'Name': 'Mixed', 'Type': 'CollectionFolder', 'IsFolder': true},
'Name': 'Mixed', {'Id': 'view-empty-type', 'Name': 'Mixed', 'Type': 'CollectionFolder', 'CollectionType': '', 'IsFolder': true},
'CollectionType': 'mixed', ]) {
}, serverId: ServerId(_serverId))!; final mixed = JellyfinMappers.library(view, serverId: ServerId(_serverId))!;
expect(mixed.kind, MediaKind.unknown); expect(mixed.kind, MediaKind.folder);
expect(mixed.defaultBrowseKinds, const [MediaKind.movie, MediaKind.show]); expect(mixed.defaultBrowseKinds, const [MediaKind.movie, MediaKind.show]);
}
final unrecognised = JellyfinMappers.library({ final unrecognised = JellyfinMappers.library({
'Id': 'view-books', 'Id': 'view-books',
'Name': 'Books', 'Name': 'Books',
'Type': 'CollectionFolder',
'CollectionType': 'books', 'CollectionType': 'books',
}, serverId: ServerId(_serverId))!; }, serverId: ServerId(_serverId))!;
expect(unrecognised.kind, MediaKind.unknown); expect(unrecognised.kind, MediaKind.unknown);