freezer/lib/api/download/manager.dart

203 lines
6.1 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/database.dart';
import 'package:freezer/api/download/service.dart';
import 'package:freezer/api/download/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;
import 'package:path/path.dart' as p;
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 Database.open();
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 offline = true, bool isSingleton = false}) async {
//Permission
if (!offline && !(await dl.DownloadManager.checkCanDownload())) {
return false;
}
if (downloadQuality == AudioQuality.ASK) {
throw Exception('Invalid quality.');
}
if (offline) {
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);
}
// logic for downloading the track
invoke(
'addDownloads',
DownloadInfo(
trackId: track.id,
path: offline ? null : settings.downloadPath,
).toJson());
return true;
}
//Generate track download path
String? _generatePath(Track track, bool offline,
{String? playlistName,
int? playlistTrackNumber,
bool isSingleton = false}) {
var path = settings.downloadPath!;
if (settings.playlistFolder && playlistName != null) {
path = p.join(path, Database.sanitize(playlistName));
}
if (settings.artistFolder) path = p.join(path, '%albumArtist%');
//Album folder / with disk number
if (settings.albumFolder) {
if (settings.albumDiscFolder) {
path = p.join(path, '%album% - Disk ${track.diskNumber ?? 1}');
} else {
path = p.join(path, '%album%');
}
}
//Final path
path = p.join(
path,
isSingleton ? settings.singletonFilename : settings.downloadFilename,
);
//Playlist track number variable (not accessible in service)
if (playlistTrackNumber != null) {
path = path.replaceAll(
'%playlistTrackNumber%', playlistTrackNumber.toString());
path = path.replaceAll('%0playlistTrackNumber%',
playlistTrackNumber.toString().padLeft(2, '0'));
} else {
path = path.replaceAll('%playlistTrackNumber%', '');
path = path.replaceAll('%0playlistTrackNumber%', '');
}
return path;
}
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();
}