import 'dart:async'; import 'dart:io'; import 'dart:isolate'; import 'package:flutter/widgets.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/definitions.dart' as d; import 'package:freezer/api/download_manager/database.dart'; import 'package:freezer/api/download_manager/download_service.dart'; import 'package:freezer/api/download_manager/service_interface.dart'; import 'package:freezer/api/paths.dart'; import 'package:freezer/main.dart'; import 'package:freezer/settings.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:isar/isar.dart'; import '../download.dart' as dl; class DownloadManager { //implements dl.DownloadManager { late Isar _isar; Isolate? _isolate; ServiceInterface? _service; bool _started = false; Future startDebug() async { if (_started) return; _started = true; await configure(); await startService(); await Future.delayed(const Duration(milliseconds: 500)); } Future configure() async { _isar = await Isar.open( [ // collections TrackSchema, AlbumSchema, ArtistSchema, PlaylistSchema, ], directory: await Paths.dataDirectory(), name: 'offline', ); if (Platform.isAndroid || Platform.isIOS) { return FlutterBackgroundService().configure( iosConfiguration: IosConfiguration(), // fuck ios androidConfiguration: AndroidConfiguration( onStart: _startNative, isForegroundMode: false, autoStart: false, autoStartOnBoot: false, foregroundServiceNotificationId: DownloadService.NOTIFICATION_ID, notificationChannelId: DownloadService.NOTIFICATION_CHANNEL_ID, initialNotificationTitle: 'Freezer'.i18n, initialNotificationContent: 'Starting download service...'.i18n, )); } // will run in foreground instead, in a separate isolate return Future.value(true); } Future startService() async { if (Platform.isAndroid) { final didStart = await FlutterBackgroundService().startService(); if (!didStart) return false; } else { // UI -> Service communication final receivePort = ReceivePort(); _isolate = await Isolate.spawn(_startIsolate, receivePort.sendPort); _service = ServiceInterface(receivePort: receivePort); _service!.on('sendPort', (args) { _service!.sendPort = args!['s']; _service!.no('sendPort'); }); } final completer = Completer(); on('ready', (args) => completer.complete()); await completer.future; invoke('updateSettings', { 'downloadFilename': settings.downloadFilename, 'deezerLanguage': settings.deezerLanguage, 'deezerCountry': settings.deezerCountry, }); return true; } void kill() { _isolate?.kill(); } void invoke(String method, [Map? args]) { if (_service != null) { _service!.send(method, args); return; } FlutterBackgroundService().invoke(method, args); } void on(String method, ListenerCallback listener) { if (_service != null) { _service!.on(method, listener); return; } FlutterBackgroundService().on(method).listen(listener); } Future checkOffline(String trackId) async { final c = await _isar.tracks.where().isarIdEqualTo(int.parse(trackId)).count(); return c > 0; } Future addOfflineTrack(d.Track track, AudioQuality downloadQuality, {bool private = true}) async { //Permission if (!private && !(await dl.DownloadManager.checkCanDownload())) { return false; } if (downloadQuality == AudioQuality.ASK) { throw Exception('Invalid quality.'); } if (private) { if (track.artists == null || track.artists!.isEmpty || track.album == null) { track = await DeezerAPI.instance.track(track.id); } // cache album art cacheManager.getSingleFile(track.albumArt!.thumb); cacheManager.getSingleFile(track.albumArt!.full); await _isar.writeTxn(() async { await _isar.tracks.put(Track.from(track)); if (track.album != null) { await _isar.albums.put(Album.from(track.album!)); } if (track.artists != null) { await _isar.artists .putAll(track.artists!.map(Artist.from).toList(growable: false)); } }); } // logic for downloading the track invoke( 'addDownloads', DownloadInfo( trackId: track.id, path: private ? null : settings.downloadPath, ).toJson()); return true; } static void _startNative(ServiceInstance service) => _startService(ServiceInterface(service: service)); static void _startIsolate(SendPort sendPort) async { final receivePort = ReceivePort(); final service = ServiceInterface(receivePort: receivePort) ..sendPort = sendPort; service.send('sendPort', {'s': receivePort.sendPort}); return _startService(service); } static void _startService(ServiceInterface service) => DownloadService(service).run(); }