Vaani/lib/api/library_browser_provider.dart
Claude 9e0f25f0e2
fix: try multiple endpoints and improve series parsing
- Try filterdata endpoint first (like genres)
- Fall back to series endpoint if needed
- Make SimpleSeries.fromJson more robust with multiple field name attempts
- Add extensive logging to debug series loading issues
2025-11-20 19:30:18 +00:00

162 lines
5.1 KiB
Dart

import 'package:hooks_riverpod/hooks_riverpod.dart' show Ref;
import 'package:logging/logging.dart' show Logger;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shelfsdk/audiobookshelf_api.dart' show Author;
import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider;
import 'package:vaani/api/library_provider.dart' show currentLibraryProvider;
part 'library_browser_provider.g.dart';
/// Simple series data for display purposes
class SimpleSeries {
final String id;
final String name;
final int? numBooks;
SimpleSeries({
required this.id,
required this.name,
this.numBooks,
});
factory SimpleSeries.fromJson(Map<String, dynamic> json) {
try {
return SimpleSeries(
id: json['id'] as String,
name: json['name'] as String,
numBooks: json['numBooks'] as int? ??
json['num_books'] as int? ??
(json['books'] as List?)?.length,
);
} catch (e) {
_logger.warning('Error parsing series: $e. JSON: $json');
rethrow;
}
}
}
final _logger = Logger('LibraryBrowserProvider');
/// Provider for fetching all authors in the current library
@riverpod
Future<List<Author>> libraryAuthors(Ref ref) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return [];
}
final authors = await api.libraries.getAuthors(libraryId: currentLibrary.id);
if (authors == null) {
_logger.warning('Failed to fetch authors for library ${currentLibrary.id}');
return [];
}
_logger.fine('Fetched ${authors.length} authors');
return authors;
}
/// Provider for fetching all genres in the current library
@riverpod
Future<List<String>> libraryGenres(Ref ref) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return [];
}
// Use raw API call to avoid Series deserialization issues in LibraryFilterData
final genres = await api.getJson<List<String>>(
path: '/api/libraries/${currentLibrary.id}/filterdata',
requiresAuth: true,
fromJson: (json) {
if (json is Map<String, dynamic> && json.containsKey('genres')) {
final genresList = json['genres'] as List<dynamic>;
return genresList.map((e) => e.toString()).toList();
}
return <String>[];
},
);
if (genres == null) {
_logger.warning('Failed to fetch genres for library ${currentLibrary.id}');
return [];
}
_logger.fine('Fetched ${genres.length} genres');
return genres;
}
/// Provider for fetching all series in the current library
@riverpod
Future<List<SimpleSeries>> librarySeries(Ref ref) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return [];
}
try {
// First try: Get from filterdata endpoint (same as genres)
final filterDataSeries = await api.getJson<List<SimpleSeries>>(
path: '/api/libraries/${currentLibrary.id}/filterdata',
requiresAuth: true,
fromJson: (json) {
_logger.info('FilterData API response keys: ${json is Map ? (json as Map).keys : json.runtimeType}');
if (json is Map<String, dynamic> && json.containsKey('series')) {
final seriesList = json['series'] as List<dynamic>;
_logger.info('Found ${seriesList.length} series in filterdata');
return seriesList
.map((e) => SimpleSeries.fromJson(e as Map<String, dynamic>))
.toList();
}
return <SimpleSeries>[];
},
);
if (filterDataSeries != null && filterDataSeries.isNotEmpty) {
_logger.fine('Fetched ${filterDataSeries.length} series from filterdata');
return filterDataSeries;
}
// Second try: Get from series endpoint
_logger.info('Trying series endpoint...');
final seriesList = await api.getJson<List<SimpleSeries>>(
path: '/api/libraries/${currentLibrary.id}/series',
requiresAuth: true,
fromJson: (json) {
_logger.info('Series API response keys: ${json is Map ? (json as Map).keys : json.runtimeType}');
if (json is Map<String, dynamic>) {
if (json.containsKey('results')) {
final results = json['results'] as List<dynamic>;
_logger.info('Found ${results.length} series in results');
return results
.map((e) => SimpleSeries.fromJson(e as Map<String, dynamic>))
.toList();
}
}
return <SimpleSeries>[];
},
);
if (seriesList == null) {
_logger.warning('Failed to fetch series for library ${currentLibrary.id}');
return [];
}
_logger.fine('Fetched ${seriesList.length} series');
return seriesList;
} catch (e, stackTrace) {
_logger.severe('Error fetching series: $e', e, stackTrace);
rethrow;
}
}