184 lines
5.4 KiB
Dart
184 lines
5.4 KiB
Dart
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<void> startDebug() async {
|
|
if (_started) return;
|
|
_started = true;
|
|
|
|
await configure();
|
|
await startService();
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
|
}
|
|
|
|
Future<bool> 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<bool> 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<void>();
|
|
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<String, dynamic>? 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<bool> checkOffline(String trackId) async {
|
|
final c =
|
|
await _isar.tracks.where().isarIdEqualTo(int.parse(trackId)).count();
|
|
return c > 0;
|
|
}
|
|
|
|
Future<bool> 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();
|
|
}
|