import 'dart:async'; import 'dart:io'; // import 'package:background_downloader/background_downloader.dart'; import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:path/path.dart'; import 'package:piped_client/piped_client.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart'; import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/provider/piped_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class DownloadManagerProvider extends StateNotifier> { final Ref ref; final StreamController /* */ activeDownloadProgress; final StreamController /* */ failedDownloads; Track? _activeItem; FutureOr Function(Track)? onFileExists; DownloadManagerProvider(this.ref) : activeDownloadProgress = StreamController.broadcast(), failedDownloads = StreamController.broadcast(), super([]) { // FileDownloader().registerCallbacks( // group: FileDownloader.defaultGroup, // taskNotificationTapCallback: (task, notificationType) { // router.go("/library"); // }, // taskStatusCallback: (update) async { // if (update.status == TaskStatus.running) { // _activeItem = // state.firstWhereOrNull((track) => track.id == update.task.taskId); // state = state.toList(); // } // if (update.status == TaskStatus.failed || // update.status == TaskStatus.notFound) { // failedDownloads.add(update.task); // } // if (update.status == TaskStatus.complete) { // final track = // state.firstWhere((element) => element.id == update.task.taskId); // // resetting the replace downloaded file state on queue completion // if (state.last == track) { // ref.read(replaceDownloadedFileState.notifier).state = null; // } // state = state // .where((element) => element.id != update.task.taskId) // .toList(); // final imageUri = TypeConversionUtils.image_X_UrlString( // track.album?.images ?? [], // placeholder: ImagePlaceholder.online, // ); // final response = await get(Uri.parse(imageUri)); // final tempFile = File(await update.task.filePath()); // final file = tempFile.copySync(_getPathForTrack(track)); // await tempFile.delete(); // await MetadataGod.writeMetadata( // file: file.path, // metadata: Metadata( // title: track.name, // artist: track.artists?.map((a) => a.name).join(", "), // album: track.album?.name, // albumArtist: track.artists?.map((a) => a.name).join(", "), // year: track.album?.releaseDate != null // ? int.tryParse(track.album!.releaseDate!) // : null, // trackNumber: track.trackNumber, // discNumber: track.discNumber, // durationMs: track.durationMs?.toDouble(), // fileSize: file.lengthSync(), // trackTotal: track.album?.tracks?.length, // picture: response.headers['content-type'] != null // ? Picture( // data: response.bodyBytes, // mimeType: response.headers['content-type']!, // ) // : null, // ), // ); // } // }, // taskProgressCallback: (update) { // activeDownloadProgress.add(update); // }, // ); // FileDownloader().trackTasks(markDownloadedComplete: true); } UserPreferences get preferences => ref.read(userPreferencesProvider); PipedClient get pipedClient => ref.read(pipedClientProvider); int get totalDownloads => state.length; List get items => state; Track? get activeItem => _activeItem; String _getPathForTrack(Track track) => join( preferences.downloadLocation, "${track.name} - ${track.artists?.map((a) => a.name).join(", ")}.m4a", ); Future /* */ _ensureSpotubeTrack(Track track) async { if (state.any((element) => element.id == track.id)) { final task = null /* await FileDownloader().taskForId(track.id!) */; if (task != null) { return task; } // this makes sure we already have the fetched track track = state.firstWhere((element) => element.id == track.id); state.removeWhere((element) => element.id == track.id); } final spotubeTrack = track is SpotubeTrack ? track : await SpotubeTrack.fetchFromTrack( track, preferences, pipedClient, ); state = [...state, spotubeTrack]; // final task = DownloadTask( // url: spotubeTrack.ytUri, // baseDirectory: BaseDirectory.applicationSupport, // taskId: spotubeTrack.id!, // updates: Updates.statusAndProgress, // ); // return task; return null; } Future /* */ enqueue(Track track) async { final replaceFileGlobal = ref.read(replaceDownloadedFileState); final file = File(_getPathForTrack(track)); if (file.existsSync() && (replaceFileGlobal ?? await onFileExists?.call(track)) != true) { if (state.isEmpty) { ref.read(replaceDownloadedFileState.notifier).state = null; } return null; } final task = await _ensureSpotubeTrack(track); // await FileDownloader().enqueue(task); return task; } Future */ > enqueueAll(List tracks) async { final tasks = await Future.wait(tracks.mapIndexed((i, e) { if (i != 0) { /// One second delay between each download to avoid /// clogging the Piped server with too many requests return Future.delayed(const Duration(seconds: 1), () => enqueue(e)); } return enqueue(e); })); if (tasks.isEmpty) { ref.read(replaceDownloadedFileState.notifier).state = null; } return tasks. /* whereType(). */ toList(); } Future cancel(Track track) async { // await FileDownloader().cancelTaskWithId(track.id!); state = state.where((element) => element.id != track.id).toList(); } Future cancelAll() async { // (await FileDownloader().reset()); state = []; } } final downloadManagerProvider = StateNotifierProvider>( DownloadManagerProvider.new, );