Compare commits
2 commits
c7cf4c30b7
...
3f9c9d5027
Author | SHA1 | Date | |
---|---|---|---|
|
3f9c9d5027 | ||
|
1257a81e90 |
|
@ -538,9 +538,8 @@ class DownloadManager {
|
||||||
results.playlists!.add((await getPlaylist(playlist['id'])));
|
results.playlists!.add((await getPlaylist(playlist['id'])));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
} //Sanitize filename
|
||||||
|
|
||||||
//Sanitize filename
|
|
||||||
String sanitize(String input) {
|
String sanitize(String input) {
|
||||||
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
|
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
|
||||||
return input.replaceAll(sanitize, '');
|
return input.replaceAll(sanitize, '');
|
||||||
|
@ -575,8 +574,10 @@ class DownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Final path
|
//Final path
|
||||||
path = p.join(path!,
|
path = p.join(
|
||||||
isSingleton ? settings.singletonFilename : settings.downloadFilename);
|
path!,
|
||||||
|
isSingleton ? settings.singletonFilename : settings.downloadFilename,
|
||||||
|
);
|
||||||
//Playlist track number variable (not accessible in service)
|
//Playlist track number variable (not accessible in service)
|
||||||
if (playlistTrackNumber != null) {
|
if (playlistTrackNumber != null) {
|
||||||
path = path.replaceAll(
|
path = path.replaceAll(
|
||||||
|
|
|
@ -1,9 +1,27 @@
|
||||||
import 'package:freezer/api/definitions.dart' as d;
|
import 'package:freezer/api/definitions.dart' as d;
|
||||||
import 'package:freezer/api/definitions.dart' show AlbumType;
|
import 'package:freezer/api/definitions.dart' show AlbumType;
|
||||||
|
import 'package:freezer/api/paths.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
part 'database.g.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
|
@collection
|
||||||
class Track {
|
class Track {
|
||||||
Id get isarId => int.parse(id);
|
Id get isarId => int.parse(id);
|
|
@ -6,15 +6,16 @@ import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/definitions.dart' as d;
|
import 'package:freezer/api/definitions.dart' as d;
|
||||||
import 'package:freezer/api/download_manager/database.dart';
|
import 'package:freezer/api/download/database.dart';
|
||||||
import 'package:freezer/api/download_manager/download_service.dart';
|
import 'package:freezer/api/download/service.dart';
|
||||||
import 'package:freezer/api/download_manager/service_interface.dart';
|
import 'package:freezer/api/download/service_interface.dart';
|
||||||
import 'package:freezer/api/paths.dart';
|
import 'package:freezer/api/paths.dart';
|
||||||
import 'package:freezer/main.dart';
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import '../download.dart' as dl;
|
import '../download.dart' as dl;
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
class DownloadManager {
|
class DownloadManager {
|
||||||
//implements dl.DownloadManager {
|
//implements dl.DownloadManager {
|
||||||
|
@ -36,17 +37,7 @@ class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> configure() async {
|
Future<bool> configure() async {
|
||||||
_isar = await Isar.open(
|
_isar = await Database.open();
|
||||||
[
|
|
||||||
// collections
|
|
||||||
TrackSchema,
|
|
||||||
AlbumSchema,
|
|
||||||
ArtistSchema,
|
|
||||||
PlaylistSchema,
|
|
||||||
],
|
|
||||||
directory: await Paths.dataDirectory(),
|
|
||||||
name: 'offline',
|
|
||||||
);
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
return FlutterBackgroundService().configure(
|
return FlutterBackgroundService().configure(
|
||||||
iosConfiguration: IosConfiguration(), // fuck ios
|
iosConfiguration: IosConfiguration(), // fuck ios
|
||||||
|
@ -123,16 +114,16 @@ class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addOfflineTrack(d.Track track, AudioQuality downloadQuality,
|
Future<bool> addOfflineTrack(d.Track track, AudioQuality downloadQuality,
|
||||||
{bool private = true}) async {
|
{bool offline = true, bool isSingleton = false}) async {
|
||||||
//Permission
|
//Permission
|
||||||
if (!private && !(await dl.DownloadManager.checkCanDownload())) {
|
if (!offline && !(await dl.DownloadManager.checkCanDownload())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadQuality == AudioQuality.ASK) {
|
if (downloadQuality == AudioQuality.ASK) {
|
||||||
throw Exception('Invalid quality.');
|
throw Exception('Invalid quality.');
|
||||||
}
|
}
|
||||||
if (private) {
|
if (offline) {
|
||||||
if (track.artists == null ||
|
if (track.artists == null ||
|
||||||
track.artists!.isEmpty ||
|
track.artists!.isEmpty ||
|
||||||
track.album == null) {
|
track.album == null) {
|
||||||
|
@ -142,18 +133,6 @@ class DownloadManager {
|
||||||
// cache album art
|
// cache album art
|
||||||
cacheManager.getSingleFile(track.albumArt!.thumb);
|
cacheManager.getSingleFile(track.albumArt!.thumb);
|
||||||
cacheManager.getSingleFile(track.albumArt!.full);
|
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
|
// logic for downloading the track
|
||||||
|
@ -161,12 +140,52 @@ class DownloadManager {
|
||||||
'addDownloads',
|
'addDownloads',
|
||||||
DownloadInfo(
|
DownloadInfo(
|
||||||
trackId: track.id,
|
trackId: track.id,
|
||||||
path: private ? null : settings.downloadPath,
|
path: offline ? null : settings.downloadPath,
|
||||||
).toJson());
|
).toJson());
|
||||||
|
|
||||||
return true;
|
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) =>
|
static void _startNative(ServiceInstance service) =>
|
||||||
_startService(ServiceInterface(service: service));
|
_startService(ServiceInterface(service: service));
|
||||||
|
|
|
@ -3,15 +3,18 @@ import 'dart:io';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/deezer_audio.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.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/main.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/settings.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
part 'download_service.freezed.dart';
|
part 'service.freezed.dart';
|
||||||
part 'download_service.g.dart';
|
part 'service.g.dart';
|
||||||
|
|
||||||
class DownloadService {
|
class DownloadService {
|
||||||
static const NOTIFICATION_ID = 6969;
|
static const NOTIFICATION_ID = 6969;
|
||||||
|
@ -28,6 +31,8 @@ class DownloadService {
|
||||||
|
|
||||||
late DeezerAPI _deezerAPI;
|
late DeezerAPI _deezerAPI;
|
||||||
|
|
||||||
|
late final Isar _isar;
|
||||||
|
|
||||||
void run() async {
|
void run() async {
|
||||||
service.on('addDownloads', (event) {
|
service.on('addDownloads', (event) {
|
||||||
downloadTrack(DownloadInfo.fromJson(event!));
|
downloadTrack(DownloadInfo.fromJson(event!));
|
||||||
|
@ -57,25 +62,24 @@ class DownloadService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_isar = await Database.open();
|
||||||
_deezerAPI = DeezerAPI(await getCookieJar());
|
_deezerAPI = DeezerAPI(await getCookieJar());
|
||||||
await _deezerAPI.authorize();
|
|
||||||
|
|
||||||
service.send('ready');
|
service.send('ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadTrack(DownloadInfo info) async {
|
void downloadTrack(DownloadInfo info) async {
|
||||||
|
await _deezerAPI.authorize();
|
||||||
|
|
||||||
final trackId = info.trackId;
|
final trackId = info.trackId;
|
||||||
final deezerAudio = DeezerAudio(
|
final deezerAudio = DeezerAudio(
|
||||||
deezerAPI: _deezerAPI,
|
deezerAPI: _deezerAPI,
|
||||||
quality: _downloadQuality ?? AudioQuality.MP3_128,
|
quality: _downloadQuality ?? AudioQuality.MP3_128,
|
||||||
trackId: trackId);
|
trackId: trackId);
|
||||||
|
|
||||||
|
bool offline = info.path == null;
|
||||||
|
|
||||||
final track = await _deezerAPI.track(trackId);
|
final track = await _deezerAPI.track(trackId);
|
||||||
final file = File(join(
|
final file = File(fillVariables(track, info.path!));
|
||||||
info.path!,
|
|
||||||
downloadFilename
|
|
||||||
.replaceAll('%artist%', track.artists?.join(',') ?? '')
|
|
||||||
.replaceAll('%title%', track.title!)));
|
|
||||||
print('downloading to ${file.path}');
|
print('downloading to ${file.path}');
|
||||||
final Uri uri;
|
final Uri uri;
|
||||||
if (useGetURL) {
|
if (useGetURL) {
|
||||||
|
@ -90,6 +94,18 @@ class DownloadService {
|
||||||
uri = res.uri;
|
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
|
// download url and decrypt
|
||||||
final req = await _deezerAPI.dio.get(uri.toString(),
|
final req = await _deezerAPI.dio.get(uri.toString(),
|
||||||
options: Options(responseType: ResponseType.bytes));
|
options: Options(responseType: ResponseType.bytes));
|
||||||
|
@ -99,6 +115,26 @@ class DownloadService {
|
||||||
await stream.pipe(fWrite);
|
await stream.pipe(fWrite);
|
||||||
print('download complete!');
|
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
|
@freezed
|
|
@ -47,6 +47,8 @@ class Paths {
|
||||||
.path;
|
.path;
|
||||||
}
|
}
|
||||||
return target.path;
|
return target.path;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return (await getApplicationSupportDirectory()).path;
|
||||||
default:
|
default:
|
||||||
return (await getApplicationDocumentsDirectory()).path;
|
return (await getApplicationDocumentsDirectory()).path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,7 @@ void main() async {
|
||||||
GetIt.instance.registerSingleton<CookieJar>(await getCookieJar());
|
GetIt.instance.registerSingleton<CookieJar>(await getCookieJar());
|
||||||
final deezerAPI =
|
final deezerAPI =
|
||||||
GetIt.instance.registerSingleton<DeezerAPI>(DeezerAPI(cookieJar));
|
GetIt.instance.registerSingleton<DeezerAPI>(DeezerAPI(cookieJar));
|
||||||
|
|
||||||
deezerAPI.deezerCountry = settings.deezerCountry;
|
deezerAPI.deezerCountry = settings.deezerCountry;
|
||||||
deezerAPI.deezerLanguage = settings.deezerLanguage;
|
deezerAPI.deezerLanguage = settings.deezerLanguage;
|
||||||
deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId;
|
deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId;
|
||||||
|
@ -141,7 +142,7 @@ void main() async {
|
||||||
'${record.level.name}: ${record.time}: [${record.loggerName}] ${record.message}');
|
'${record.level.name}: ${record.time}: [${record.loggerName}] ${record.message}');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (kDebugMode || true) {
|
if (kDebugMode) {
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import 'package:freezer/ui/cached_image.dart';
|
||||||
import 'package:numberpicker/numberpicker.dart';
|
import 'package:numberpicker/numberpicker.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:freezer/api/download_manager/download_manager.dart' as newDl;
|
|
||||||
|
|
||||||
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
|
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
|
||||||
final Track track;
|
final Track track;
|
||||||
|
@ -297,7 +296,7 @@ class MenuSheet {
|
||||||
Text('Download'.i18n),
|
Text('Download'.i18n),
|
||||||
icon: const Icon(Icons.file_download),
|
icon: const Icon(Icons.file_download),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final dl = newDl.DownloadManager();
|
// final dl = newDl.DownloadManager();
|
||||||
// await dl.startDebug();
|
// await dl.startDebug();
|
||||||
// dl.addOfflineTrack(t, settings.downloadQuality, private: false);
|
// dl.addOfflineTrack(t, settings.downloadQuality, private: false);
|
||||||
if (await downloadManager.addOfflineTrack(t,
|
if (await downloadManager.addOfflineTrack(t,
|
||||||
|
|
|
@ -1028,8 +1028,7 @@ class _FilenameTemplateDialogState extends State<FilenameTemplateDialog> {
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('Reset'.i18n),
|
child: Text('Reset'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_controller!.value =
|
_controller!.text = '%artist% - %title%';
|
||||||
_controller!.value.copyWith(text: '%artist% - %title%');
|
|
||||||
_new = '%artist% - %title%';
|
_new = '%artist% - %title%';
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue