refactor(paging): share unknown-total sentinel
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user