work on download backend + try fix ios

This commit is contained in:
Pato05 2024-03-31 19:29:06 +02:00
parent f88e43cad9
commit 1257a81e90
No known key found for this signature in database
GPG Key ID: F53CA394104BA0CB
9 changed files with 129 additions and 54 deletions

View File

@ -538,9 +538,8 @@ class DownloadManager {
results.playlists!.add((await getPlaylist(playlist['id'])));
}
return results;
}
} //Sanitize filename
//Sanitize filename
String sanitize(String input) {
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
return input.replaceAll(sanitize, '');
@ -575,8 +574,10 @@ class DownloadManager {
}
}
//Final path
path = p.join(path!,
isSingleton ? settings.singletonFilename : settings.downloadFilename);
path = p.join(
path!,
isSingleton ? settings.singletonFilename : settings.downloadFilename,
);
//Playlist track number variable (not accessible in service)
if (playlistTrackNumber != null) {
path = path.replaceAll(

View File

@ -1,9 +1,27 @@
import 'package:freezer/api/definitions.dart' as d;
import 'package:freezer/api/definitions.dart' show AlbumType;
import 'package:freezer/api/paths.dart';
import 'package:isar/isar.dart';
part 'database.g.dart';
class Database {
static Future<Isar> open() async => Isar.open(
[
// collections
TrackSchema,
AlbumSchema,
ArtistSchema,
PlaylistSchema,
],
directory: await Paths.dataDirectory(),
name: 'offline',
);
static String sanitize(String input) =>
input.replaceAll(RegExp(r'[\/\\\?\%\*\:\|\"\<\>]'), '');
}
@collection
class Track {
Id get isarId => int.parse(id);

View File

@ -6,15 +6,16 @@ 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/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 {
@ -36,17 +37,7 @@ class DownloadManager {
}
Future<bool> configure() async {
_isar = await Isar.open(
[
// collections
TrackSchema,
AlbumSchema,
ArtistSchema,
PlaylistSchema,
],
directory: await Paths.dataDirectory(),
name: 'offline',
);
_isar = await Database.open();
if (Platform.isAndroid || Platform.isIOS) {
return FlutterBackgroundService().configure(
iosConfiguration: IosConfiguration(), // fuck ios
@ -123,16 +114,16 @@ class DownloadManager {
}
Future<bool> addOfflineTrack(d.Track track, AudioQuality downloadQuality,
{bool private = true}) async {
{bool offline = true, bool isSingleton = false}) async {
//Permission
if (!private && !(await dl.DownloadManager.checkCanDownload())) {
if (!offline && !(await dl.DownloadManager.checkCanDownload())) {
return false;
}
if (downloadQuality == AudioQuality.ASK) {
throw Exception('Invalid quality.');
}
if (private) {
if (offline) {
if (track.artists == null ||
track.artists!.isEmpty ||
track.album == null) {
@ -142,18 +133,6 @@ class DownloadManager {
// 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
@ -161,12 +140,52 @@ class DownloadManager {
'addDownloads',
DownloadInfo(
trackId: track.id,
path: private ? null : settings.downloadPath,
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));

View File

@ -3,15 +3,18 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/deezer_audio.dart';
import 'package:freezer/api/definitions.dart' as d;
import 'package:freezer/api/download.dart';
import 'package:freezer/api/download_manager/service_interface.dart';
import 'package:freezer/api/download/database.dart';
import 'package:freezer/api/download/service_interface.dart';
import 'package:freezer/main.dart';
import 'package:freezer/settings.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:isar/isar.dart';
import 'package:path/path.dart';
part 'download_service.freezed.dart';
part 'download_service.g.dart';
part 'service.freezed.dart';
part 'service.g.dart';
class DownloadService {
static const NOTIFICATION_ID = 6969;
@ -28,6 +31,8 @@ class DownloadService {
late DeezerAPI _deezerAPI;
late final Isar _isar;
void run() async {
service.on('addDownloads', (event) {
downloadTrack(DownloadInfo.fromJson(event!));
@ -57,25 +62,24 @@ class DownloadService {
}
});
_isar = await Database.open();
_deezerAPI = DeezerAPI(await getCookieJar());
await _deezerAPI.authorize();
service.send('ready');
}
void downloadTrack(DownloadInfo info) async {
await _deezerAPI.authorize();
final trackId = info.trackId;
final deezerAudio = DeezerAudio(
deezerAPI: _deezerAPI,
quality: _downloadQuality ?? AudioQuality.MP3_128,
trackId: trackId);
bool offline = info.path == null;
final track = await _deezerAPI.track(trackId);
final file = File(join(
info.path!,
downloadFilename
.replaceAll('%artist%', track.artists?.join(',') ?? '')
.replaceAll('%title%', track.title!)));
final file = File(fillVariables(track, info.path!));
print('downloading to ${file.path}');
final Uri uri;
if (useGetURL) {
@ -90,6 +94,18 @@ class DownloadService {
uri = res.uri;
}
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));
}
});
// download url and decrypt
final req = await _deezerAPI.dio.get(uri.toString(),
options: Options(responseType: ResponseType.bytes));
@ -99,6 +115,26 @@ class DownloadService {
await stream.pipe(fWrite);
print('download complete!');
}
String pathForOffline(d.Track t) {
// TODO: implement
throw Exception();
}
String fillVariables(d.Track t, String path) {
return path
.replaceAll('%title%', Database.sanitize(t.title!))
.replaceAll('%album%', t.album!.title!)
.replaceAll('%artist%', t.artists?[0].name ?? '')
.replaceAll('%albumArtist%',
Database.sanitize(t.album?.artistString ?? t.artistString))
.replaceAll('%artists%', t.artistString)
.replaceAll('%feats%',
t.artists?.sublist(1).map((e) => e.name!).join(', ') ?? '')
.replaceAll('%trackNumber%', t.trackNumber!.toString())
.replaceAll('%0trackNumber%', t.trackNumber.toString().padLeft(2, '0'))
.replaceAll('%year%', '0'); // TODO: find year
}
}
@freezed

View File

@ -47,6 +47,8 @@ class Paths {
.path;
}
return target.path;
case TargetPlatform.iOS:
return (await getApplicationSupportDirectory()).path;
default:
return (await getApplicationDocumentsDirectory()).path;
}

View File

@ -131,6 +131,7 @@ void main() async {
GetIt.instance.registerSingleton<CookieJar>(await getCookieJar());
final deezerAPI =
GetIt.instance.registerSingleton<DeezerAPI>(DeezerAPI(cookieJar));
deezerAPI.deezerCountry = settings.deezerCountry;
deezerAPI.deezerLanguage = settings.deezerLanguage;
deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId;
@ -141,7 +142,7 @@ void main() async {
'${record.level.name}: ${record.time}: [${record.loggerName}] ${record.message}');
});
if (kDebugMode || true) {
if (kDebugMode) {
Logger.root.level = Level.ALL;
}

View File

@ -17,7 +17,6 @@ import 'package:freezer/ui/cached_image.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:freezer/api/download_manager/download_manager.dart' as newDl;
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
final Track track;
@ -297,12 +296,12 @@ class MenuSheet {
Text('Download'.i18n),
icon: const Icon(Icons.file_download),
onTap: () async {
final dl = newDl.DownloadManager();
await dl.startDebug();
dl.addOfflineTrack(t, settings.downloadQuality, private: false);
//if (await downloadManager.addOfflineTrack(t,
// private: false, context: context, isSingleton: true) !=
// false) showDownloadStartedToast();
// final dl = newDl.DownloadManager();
// await dl.startDebug();
// dl.addOfflineTrack(t, settings.downloadQuality, private: false);
if (await downloadManager.addOfflineTrack(t,
private: false, context: context, isSingleton: true) !=
false) showDownloadStartedToast();
},
);

View File

@ -1028,8 +1028,7 @@ class _FilenameTemplateDialogState extends State<FilenameTemplateDialog> {
TextButton(
child: Text('Reset'.i18n),
onPressed: () {
_controller!.value =
_controller!.value.copyWith(text: '%artist% - %title%');
_controller!.text = '%artist% - %title%';
_new = '%artist% - %title%';
},
),