Vaani/lib/features/downloads/core/download_manager.dart

227 lines
6.9 KiB
Dart

// download manager to handle download tasks of files
import 'dart:async';
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shelfsdk/audiobookshelf_api.dart';
import 'package:vaani/shared/extensions/model_conversions.dart';
final _logger = Logger('AudiobookDownloadManager');
final tq = MemoryTaskQueue();
const downloadDirectory = BaseDirectory.applicationSupport;
class AudiobookDownloadManager {
// takes in the baseUrl and the token
AudiobookDownloadManager({
required this.baseUrl,
required this.token,
this.requiresWiFi = true,
this.retries = 0,
this.allowPause = false,
// /// The maximum number of concurrent tasks to run at any given time.
// int maxConcurrent = 3,
// /// The maximum number of concurrent tasks to run for the same host.
// int maxConcurrentByHost = 2,
// /// The maximum number of concurrent tasks to run for the same group.
// int maxConcurrentByGroup = 3,
}) {
// initialize the download manager
FileDownloader().addTaskQueue(tq);
_logger.fine('initialized with baseUrl: $baseUrl, token: $token');
_logger.fine(
'requiresWiFi: $requiresWiFi, retries: $retries, allowPause: $allowPause',
);
initDownloadManager();
}
// the base url for the audio files
final String baseUrl;
// the authentication token to access the [AudioTrack.contentUrl]
final String token;
// whether to download only on wifi
final bool requiresWiFi;
// the number of retries to attempt
final int retries;
// whether to allow pausing of downloads
final bool allowPause;
final StreamController<TaskUpdate> _taskStatusController =
StreamController.broadcast();
Stream<TaskUpdate> get taskUpdateStream => _taskStatusController.stream;
late StreamSubscription<TaskUpdate> _updatesSubscription;
Future<void> queueAudioBookDownload(
LibraryItemExpanded item, {
void Function(TaskStatusUpdate)? taskStatusCallback,
void Function(TaskProgressUpdate)? taskProgressCallback,
void Function(Task, NotificationType)? taskNotificationTapCallback,
}) async {
_logger.info('queuing download for item: ${item.id}');
// create a download task for each file in the item
final directory = await getApplicationSupportDirectory();
for (final file in item.libraryFiles) {
// check if the file is already downloaded
if (isFileDownloaded(
constructFilePath(directory, item, file),
)) {
_logger.info('file already downloaded: ${file.metadata.filename}');
continue;
}
final task = DownloadTask(
taskId: file.ino,
url: file.url(baseUrl, item.id, token).toString(),
directory: item.relPath,
filename: file.metadata.filename,
requiresWiFi: requiresWiFi,
retries: retries,
allowPause: allowPause,
group: item.id,
baseDirectory: downloadDirectory,
// metaData: token
);
// _downloadTasks.add(task);
tq.add(task);
_logger.info('queued task: ${task.taskId}');
}
FileDownloader().registerCallbacks(
group: item.id,
taskProgressCallback: (update) {
try {
taskProgressCallback?.call(update);
} catch (e) {
_logger.warning('Error in taskProgressCallback: $e');
}
_logger.info('Group: ${item.id}, Progress Update: ${update.progress}');
},
taskStatusCallback: (update) {
try {
taskStatusCallback?.call(update);
} catch (e) {
_logger.warning('Error in taskStatusCallback: $e');
}
switch (update.status) {
case TaskStatus.complete:
_logger.info('Group: ${item.id}, Download Complete');
break;
case TaskStatus.failed:
_logger.warning('Group: ${item.id}, Download Failed');
break;
default:
_logger
.info('Group: ${item.id}, Download Status: ${update.status}');
}
},
taskNotificationTapCallback: (task, notificationType) {
try {
taskNotificationTapCallback?.call(task, notificationType);
} catch (e) {
_logger.warning('Error in taskNotificationTapCallback: $e');
}
_logger.info('Group: ${item.id}, Task: ${task.taskId}');
},
);
}
String constructFilePath(
Directory directory,
LibraryItemExpanded item,
LibraryFile file,
) =>
'${directory.path}/${item.relPath}/${file.metadata.filename}';
void dispose() {
_updatesSubscription.cancel();
FileDownloader().destroy();
_logger.fine('Destroyed download manager');
}
bool isItemDownloading(String id) {
if (tq.isEmpty) {
return false;
}
return tq.enqueued.any((task) => task.group == id);
}
bool isFileDownloaded(String filePath) {
final fileExists = File(filePath).existsSync();
return fileExists;
}
Future<bool> isItemDownloaded(LibraryItemExpanded item) async {
final directory = await getApplicationSupportDirectory();
for (final file in item.libraryFiles) {
if (!isFileDownloaded(constructFilePath(directory, item, file))) {
_logger.info('file not downloaded: ${file.metadata.filename}');
return false;
}
}
_logger.info('all files downloaded for item id: ${item.id}');
return true;
}
Future<void> deleteDownloadedItem(LibraryItemExpanded item) async {
_logger.info('deleting downloaded item id: ${item.id}');
final directory = await getApplicationSupportDirectory();
for (final file in item.libraryFiles) {
final filePath = constructFilePath(directory, item, file);
if (isFileDownloaded(filePath)) {
File(filePath).deleteSync();
}
_logger.info('deleted file: $filePath');
}
}
Future<List<Uri>> getDownloadedFiles(LibraryItemExpanded item) async {
final directory = await getApplicationSupportDirectory();
final files = <Uri>[];
for (final file in item.libraryFiles) {
final filePath = constructFilePath(directory, item, file);
if (isFileDownloaded(filePath)) {
files.add(Uri.file(filePath));
}
}
return files;
}
Future<void> initDownloadManager() async {
// initialize the download manager
_logger.info('Initializing download manager');
final fileDownloader = FileDownloader();
_logger.info('Configuring Notification');
fileDownloader.configureNotification(
running: const TaskNotification('Downloading', 'file: {filename}'),
progressBar: true,
);
await fileDownloader.trackTasks();
_logger.info('Listening to download manager updates');
try {
_updatesSubscription = fileDownloader.updates.listen((event) {
_logger.info('Got event: $event');
_taskStatusController.add(event);
});
} catch (e) {
_logger.warning('Error when listening to download manager updates: $e');
}
}
}