2024-08-20 08:36:39 -04:00
|
|
|
import 'package:background_downloader/background_downloader.dart';
|
2025-03-25 22:01:16 +05:30
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2024-09-22 22:05:28 -04:00
|
|
|
import 'package:logging/logging.dart';
|
2024-08-20 08:36:39 -04:00
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
2024-08-23 04:21:46 -04:00
|
|
|
import 'package:vaani/api/api_provider.dart';
|
2024-09-22 22:05:28 -04:00
|
|
|
import 'package:vaani/api/library_item_provider.dart';
|
|
|
|
|
import 'package:vaani/features/downloads/core/download_manager.dart' as core;
|
2024-08-23 04:21:46 -04:00
|
|
|
import 'package:vaani/settings/app_settings_provider.dart';
|
2024-09-22 22:05:28 -04:00
|
|
|
import 'package:vaani/shared/extensions/item_files.dart';
|
2024-08-20 08:36:39 -04:00
|
|
|
|
|
|
|
|
part 'download_manager.g.dart';
|
|
|
|
|
|
2024-09-22 22:05:28 -04:00
|
|
|
final _logger = Logger('AudiobookDownloadManagerProvider');
|
|
|
|
|
|
2024-08-20 08:36:39 -04:00
|
|
|
@Riverpod(keepAlive: true)
|
|
|
|
|
class SimpleDownloadManager extends _$SimpleDownloadManager {
|
|
|
|
|
@override
|
|
|
|
|
core.AudiobookDownloadManager build() {
|
|
|
|
|
final api = ref.watch(authenticatedApiProvider);
|
|
|
|
|
final downloadSettings = ref.watch(appSettingsProvider).downloadSettings;
|
|
|
|
|
|
|
|
|
|
final manager = core.AudiobookDownloadManager(
|
|
|
|
|
baseUrl: api.baseUrl.toString(),
|
|
|
|
|
token: api.token!,
|
|
|
|
|
requiresWiFi: downloadSettings.requiresWiFi,
|
|
|
|
|
retries: downloadSettings.retries,
|
|
|
|
|
allowPause: downloadSettings.allowPause,
|
|
|
|
|
);
|
|
|
|
|
core.tq.maxConcurrent = downloadSettings.maxConcurrent;
|
|
|
|
|
core.tq.maxConcurrentByHost = downloadSettings.maxConcurrentByHost;
|
|
|
|
|
core.tq.maxConcurrentByGroup = downloadSettings.maxConcurrentByGroup;
|
2024-09-22 22:05:28 -04:00
|
|
|
|
2024-08-20 08:36:39 -04:00
|
|
|
ref.onDispose(() {
|
2024-10-03 05:54:29 -04:00
|
|
|
_logger.info('disposing download manager');
|
2024-08-20 08:36:39 -04:00
|
|
|
manager.dispose();
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-03 05:54:29 -04:00
|
|
|
_logger.config('initialized download manager');
|
2024-08-20 08:36:39 -04:00
|
|
|
return manager;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-22 22:05:28 -04:00
|
|
|
@Riverpod(keepAlive: true)
|
|
|
|
|
class DownloadManager extends _$DownloadManager {
|
|
|
|
|
@override
|
|
|
|
|
core.AudiobookDownloadManager build() {
|
|
|
|
|
final manager = ref.watch(simpleDownloadManagerProvider);
|
|
|
|
|
manager.taskUpdateStream.listen((_) {
|
|
|
|
|
ref.notifyListeners();
|
|
|
|
|
});
|
|
|
|
|
return manager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> queueAudioBookDownload(
|
2024-09-22 22:24:36 -04:00
|
|
|
LibraryItemExpanded item,
|
|
|
|
|
) async {
|
2024-10-03 05:54:29 -04:00
|
|
|
_logger.fine('queueing download for ${item.id}');
|
2024-09-22 22:05:28 -04:00
|
|
|
await state.queueAudioBookDownload(
|
|
|
|
|
item,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> deleteDownloadedItem(LibraryItemExpanded item) async {
|
2024-10-03 05:54:29 -04:00
|
|
|
_logger.fine('deleting downloaded item ${item.id}');
|
2024-09-22 22:05:28 -04:00
|
|
|
await state.deleteDownloadedItem(item);
|
|
|
|
|
ref.notifyListeners();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
class IsItemDownloading extends _$IsItemDownloading {
|
|
|
|
|
@override
|
|
|
|
|
bool build(String id) {
|
|
|
|
|
final manager = ref.watch(downloadManagerProvider);
|
|
|
|
|
return manager.isItemDownloading(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
class ItemDownloadProgress extends _$ItemDownloadProgress {
|
|
|
|
|
@override
|
|
|
|
|
Future<double?> build(String id) async {
|
|
|
|
|
final item = await ref.watch(libraryItemProvider(id).future);
|
|
|
|
|
final manager = ref.read(downloadManagerProvider);
|
|
|
|
|
manager.taskUpdateStream.map((taskUpdate) {
|
|
|
|
|
if (taskUpdate is! TaskProgressUpdate) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (taskUpdate.task.group == id) {
|
|
|
|
|
return taskUpdate;
|
|
|
|
|
}
|
|
|
|
|
}).listen((task) async {
|
|
|
|
|
if (task != null) {
|
|
|
|
|
final totalSize = item.totalSize;
|
|
|
|
|
// if total size is 0, return 0
|
|
|
|
|
if (totalSize == 0) {
|
|
|
|
|
state = const AsyncValue.data(0.0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final downloadedFiles = await manager.getDownloadedFilesMetadata(item);
|
|
|
|
|
// calculate total size of downloaded files and total size of item, then divide
|
|
|
|
|
// to get percentage
|
|
|
|
|
final downloadedSize = downloadedFiles.fold<int>(
|
|
|
|
|
0,
|
|
|
|
|
(previousValue, element) => previousValue + element.metadata.size,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final inProgressFileSize = task.progress * task.expectedFileSize;
|
|
|
|
|
final totalDownloadedSize = downloadedSize + inProgressFileSize;
|
|
|
|
|
final progress = totalDownloadedSize / totalSize;
|
|
|
|
|
// if current progress is more than calculated progress, do not update
|
|
|
|
|
if (progress < (state.valueOrNull ?? 0.0)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state = AsyncValue.data(progress.clamp(0.0, 1.0));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 08:36:39 -04:00
|
|
|
@riverpod
|
|
|
|
|
FutureOr<List<TaskRecord>> downloadHistory(
|
2025-03-25 22:01:16 +05:30
|
|
|
Ref ref, {
|
2024-08-20 08:36:39 -04:00
|
|
|
String? group,
|
|
|
|
|
}) async {
|
|
|
|
|
return await FileDownloader().database.allRecords(group: group);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-22 22:05:28 -04:00
|
|
|
@riverpod
|
|
|
|
|
class IsItemDownloaded extends _$IsItemDownloaded {
|
|
|
|
|
@override
|
|
|
|
|
FutureOr<bool> build(
|
|
|
|
|
LibraryItemExpanded item,
|
|
|
|
|
) {
|
|
|
|
|
final manager = ref.watch(downloadManagerProvider);
|
|
|
|
|
return manager.isItemDownloaded(item);
|
|
|
|
|
}
|
2024-08-20 08:36:39 -04:00
|
|
|
}
|