diff --git a/lib/api/download.dart b/lib/api/download.dart index df8bf28..b9452b2 100644 --- a/lib/api/download.dart +++ b/lib/api/download.dart @@ -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( diff --git a/lib/api/download_manager/database.dart b/lib/api/download/database.dart similarity index 90% rename from lib/api/download_manager/database.dart rename to lib/api/download/database.dart index 3720a06..be648f4 100644 --- a/lib/api/download_manager/database.dart +++ b/lib/api/download/database.dart @@ -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 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); diff --git a/lib/api/download_manager/download_manager.dart b/lib/api/download/manager.dart similarity index 69% rename from lib/api/download_manager/download_manager.dart rename to lib/api/download/manager.dart index fe3b430..f0b898e 100644 --- a/lib/api/download_manager/download_manager.dart +++ b/lib/api/download/manager.dart @@ -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 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 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)); diff --git a/lib/api/download_manager/download_service.dart b/lib/api/download/service.dart similarity index 64% rename from lib/api/download_manager/download_service.dart rename to lib/api/download/service.dart index 7ed42c9..d134975 100644 --- a/lib/api/download_manager/download_service.dart +++ b/lib/api/download/service.dart @@ -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 diff --git a/lib/api/download_manager/service_interface.dart b/lib/api/download/service_interface.dart similarity index 100% rename from lib/api/download_manager/service_interface.dart rename to lib/api/download/service_interface.dart diff --git a/lib/api/paths.dart b/lib/api/paths.dart index 68cc71b..0920067 100644 --- a/lib/api/paths.dart +++ b/lib/api/paths.dart @@ -47,6 +47,8 @@ class Paths { .path; } return target.path; + case TargetPlatform.iOS: + return (await getApplicationSupportDirectory()).path; default: return (await getApplicationDocumentsDirectory()).path; } diff --git a/lib/main.dart b/lib/main.dart index ce9a4eb..1bcef15 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -131,6 +131,7 @@ void main() async { GetIt.instance.registerSingleton(await getCookieJar()); final deezerAPI = GetIt.instance.registerSingleton(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; } diff --git a/lib/ui/menu.dart b/lib/ui/menu.dart index 9ec43b7..9a61497 100644 --- a/lib/ui/menu.dart +++ b/lib/ui/menu.dart @@ -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(); }, ); diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index a7f5a7c..baa3c8e 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -1028,8 +1028,7 @@ class _FilenameTemplateDialogState extends State { TextButton( child: Text('Reset'.i18n), onPressed: () { - _controller!.value = - _controller!.value.copyWith(text: '%artist% - %title%'); + _controller!.text = '%artist% - %title%'; _new = '%artist% - %title%'; }, ),