refactor(paging): share unknown-total sentinel

This commit is contained in:
edde746
2026-07-12 08:42:21 +02:00
parent f08fceaee0
commit 777780926c
6 changed files with 28 additions and 19 deletions
+8
View File
@@ -76,3 +76,11 @@ sealed class LibraryPage<T> with _$LibraryPage<T> {
const factory LibraryPage({required List<T> items, required int totalCount, @Default(0) int offset}) =
_LibraryPage<T>;
}
/// Conservative total for a page whose backend omitted an exact count. A full
/// page adds one sentinel item so callers keep pagination enabled without
/// claiming to know the real total.
int fallbackPageTotal({required int offset, required int itemCount, int? requestedSize}) {
final fullPage = requestedSize != null && requestedSize > 0 && itemCount >= requestedSize;
return offset + itemCount + (fullPage ? 1 : 0);
}
+3 -10
View File
@@ -107,13 +107,6 @@ String _jellyfinFolderSortName(Map<String, dynamic> item) {
return raw.toLowerCase();
}
int _fallbackPageTotal({required int offset, required int itemCount, int? requestedSize}) {
if (requestedSize == null || requestedSize <= 0 || itemCount < requestedSize) {
return offset + itemCount;
}
return offset + itemCount + 1;
}
/// `/Items/Filters` is a legacy unpaged endpoint; keep failures isolated from
/// the paged Browse tab so very large libraries can still open.
const _filtersTimeout = Duration(seconds: 8);
@@ -209,7 +202,7 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin {
final totalUnreliable = isArtistQuery && rawTotal == 0 && items.isNotEmpty;
final total = rawTotal is int && !totalUnreliable
? rawTotal
: _fallbackPageTotal(offset: query.offset, itemCount: items.length, requestedSize: query.limit);
: fallbackPageTotal(offset: query.offset, itemCount: items.length, requestedSize: query.limit);
return LibraryPage<MediaItem>(items: _mapItems(items), totalCount: total, offset: query.offset);
}
@@ -1588,7 +1581,7 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin {
final rawTotal = data is Map<String, dynamic> ? data['TotalRecordCount'] : null;
final fallbackTotal = singlePage
? offset + rawItems.length
: _fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
: fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
return LibraryPage<MediaItem>(
items: _mapItems(rawItems),
totalCount: rawTotal is int ? rawTotal : fallbackTotal,
@@ -1603,7 +1596,7 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin {
LibraryPage<MediaItem> _pagedMediaItems(Object? data, {required int offset, required int requestedSize}) {
final rawItems = _itemsArray(data);
final rawTotal = data is Map<String, dynamic> ? data['TotalRecordCount'] : null;
final fallbackTotal = _fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
final fallbackTotal = fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
return LibraryPage<MediaItem>(
items: _mapItems(rawItems),
totalCount: rawTotal is int ? rawTotal : fallbackTotal,
@@ -76,7 +76,7 @@ mixin _JellyfinCollectionMethods on MediaServerCacheMixin {
LibraryPage<MediaItem> _itemsPage(Object? data, {required int offset, int? requestedSize}) {
final rawItems = _itemsArray(data);
final rawTotal = data is Map<String, dynamic> ? data['TotalRecordCount'] : null;
final fallbackTotal = _fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
final fallbackTotal = fallbackPageTotal(offset: offset, itemCount: rawItems.length, requestedSize: requestedSize);
final total = rawTotal is int ? rawTotal : fallbackTotal;
return LibraryPage<MediaItem>(items: _mapItems(rawItems), totalCount: total, offset: offset);
}
@@ -83,7 +83,7 @@ mixin _JellyfinPlaylistMethods on MediaServerCacheMixin {
final fallbackTotal = rawFinished
? filteredSeen
: _fallbackPageTotal(offset: offset, itemCount: items.length, requestedSize: pageSize);
: fallbackPageTotal(offset: offset, itemCount: items.length, requestedSize: pageSize);
return LibraryPage<MediaPlaylist>(items: items, totalCount: fallbackTotal, offset: offset);
}
@@ -134,7 +134,7 @@ mixin _JellyfinPlaylistMethods on MediaServerCacheMixin {
final rawTotal = response.data is Map<String, dynamic>
? (response.data as Map<String, dynamic>)['TotalRecordCount']
: null;
final fallbackTotal = _fallbackPageTotal(offset: offset, itemCount: items.length, requestedSize: pageSize);
final fallbackTotal = fallbackPageTotal(offset: offset, itemCount: items.length, requestedSize: pageSize);
return LibraryPage<MediaItem>(
items: _mapItems(items),
totalCount: rawTotal is int ? rawTotal : fallbackTotal,
+1 -6
View File
@@ -758,11 +758,6 @@ class PlexClient
return null;
}
int _fallbackPageTotal({required int offset, required int itemCount, int? requestedSize}) {
final fullPage = requestedSize != null && requestedSize > 0 && itemCount >= requestedSize;
return offset + itemCount + (fullPage ? 1 : 0);
}
int _responseTotalSize(MediaServerResponse response, {required int itemCount, int? start, int? requestedSize}) {
final headerTotal = _responseHeaderInt(response, 'X-Plex-Container-Total-Size');
if (headerTotal != null) return headerTotal;
@@ -776,7 +771,7 @@ class PlexClient
return flexibleInt(container?['size']) ?? itemCount;
}
return _fallbackPageTotal(offset: offset, itemCount: itemCount, requestedSize: requestedSize);
return fallbackPageTotal(offset: offset, itemCount: itemCount, requestedSize: requestedSize);
}
({List<PlexPlaylistDto> items, int totalSize}) _extractPlaylistListResult(
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/media/library_query.dart';
void main() {
test('fallbackPageTotal adds a has-more sentinel only for full pages', () {
expect(fallbackPageTotal(offset: 20, itemCount: 10, requestedSize: 10), 31);
expect(fallbackPageTotal(offset: 20, itemCount: 11, requestedSize: 10), 32);
expect(fallbackPageTotal(offset: 20, itemCount: 9, requestedSize: 10), 29);
expect(fallbackPageTotal(offset: 20, itemCount: 10), 30);
expect(fallbackPageTotal(offset: 20, itemCount: 10, requestedSize: 0), 30);
expect(fallbackPageTotal(offset: 20, itemCount: 10, requestedSize: -1), 30);
});
}