854 lines
33 KiB
Java
854 lines
33 KiB
Java
package f.f.freezer;
|
|
|
|
import android.app.NotificationChannel;
|
|
import android.app.NotificationManager;
|
|
import android.app.Service;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.Messenger;
|
|
import android.os.RemoteException;
|
|
import android.util.Log;
|
|
|
|
import androidx.core.app.NotificationCompat;
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.nio.channels.FileChannel;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.StandardCopyOption;
|
|
import java.text.DecimalFormat;
|
|
import java.util.ArrayList;
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
|
public class DownloadService extends Service {
|
|
|
|
//Message commands
|
|
static final int SERVICE_LOAD_DOWNLOADS = 1;
|
|
static final int SERVICE_START_DOWNLOAD = 2;
|
|
static final int SERVICE_ON_PROGRESS = 3;
|
|
static final int SERVICE_SETTINGS_UPDATE = 4;
|
|
static final int SERVICE_STOP_DOWNLOADS = 5;
|
|
static final int SERVICE_ON_STATE_CHANGE = 6;
|
|
static final int SERVICE_REMOVE_DOWNLOAD = 7;
|
|
static final int SERVICE_RETRY_DOWNLOADS = 8;
|
|
static final int SERVICE_REMOVE_DOWNLOADS = 9;
|
|
|
|
static final String NOTIFICATION_CHANNEL_ID = "freezerdownloads";
|
|
static final int NOTIFICATION_ID_START = 6969;
|
|
|
|
boolean running = false;
|
|
DownloadSettings settings;
|
|
Context context;
|
|
SQLiteDatabase db;
|
|
Deezer deezer = new Deezer();
|
|
|
|
Messenger serviceMessenger;
|
|
Messenger activityMessenger;
|
|
NotificationManagerCompat notificationManager;
|
|
|
|
ArrayList<Download> downloads = new ArrayList<>();
|
|
ArrayList<DownloadThread> threads = new ArrayList<>();
|
|
ArrayList<Boolean> updateRequests = new ArrayList<>();
|
|
boolean updating = false;
|
|
Handler progressUpdateHandler = new Handler();
|
|
DownloadLog logger = new DownloadLog();
|
|
|
|
public DownloadService() { }
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
|
|
//Setup notifications
|
|
context = this;
|
|
notificationManager = NotificationManagerCompat.from(context);
|
|
createNotificationChannel();
|
|
createProgressUpdateHandler();
|
|
|
|
//Setup logger, deezer api
|
|
logger.open(context);
|
|
deezer.init(logger, "");
|
|
|
|
//Get DB
|
|
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
|
db = dbHelper.getWritableDatabase();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
//Cancel notifications
|
|
notificationManager.cancelAll();
|
|
//Logger
|
|
logger.close();
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
//Set messengers
|
|
serviceMessenger = new Messenger(new IncomingHandler(this));
|
|
if (intent != null)
|
|
activityMessenger = intent.getParcelableExtra("activityMessenger");
|
|
|
|
return serviceMessenger.getBinder();
|
|
}
|
|
|
|
@Override
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
//Get messenger
|
|
if (intent != null) {
|
|
activityMessenger = intent.getParcelableExtra("activityMessenger");
|
|
}
|
|
|
|
|
|
//return super.onStartCommand(intent, flags, startId);
|
|
//Prevent battery savers I guess
|
|
return START_STICKY;
|
|
}
|
|
|
|
//Android O+ Notifications
|
|
private void createNotificationChannel() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "Downloads", NotificationManager.IMPORTANCE_MIN);
|
|
NotificationManager nManager = getSystemService(NotificationManager.class);
|
|
nManager.createNotificationChannel(channel);
|
|
}
|
|
}
|
|
|
|
//Update download tasks
|
|
private void updateQueue() {
|
|
db.beginTransaction();
|
|
|
|
//Clear downloaded tracks
|
|
for (int i=threads.size() - 1; i>=0; i--) {
|
|
Download.DownloadState state = threads.get(i).download.state;
|
|
if (state == Download.DownloadState.NONE || state == Download.DownloadState.DONE || state == Download.DownloadState.ERROR || state == Download.DownloadState.DEEZER_ERROR) {
|
|
Download d = threads.get(i).download;
|
|
//Update in queue
|
|
for (int j=0; j<downloads.size(); j++) {
|
|
if (downloads.get(j).id == d.id) {
|
|
downloads.set(j, d);
|
|
}
|
|
}
|
|
updateProgress();
|
|
//Save to DB
|
|
ContentValues row = new ContentValues();
|
|
row.put("state", state.getValue());
|
|
row.put("quality", d.quality);
|
|
db.update("Downloads", row, "id == ?", new String[]{Integer.toString(d.id)});
|
|
|
|
//Update library
|
|
if (state == Download.DownloadState.DONE && !d.priv) {
|
|
Uri uri = Uri.fromFile(new File(threads.get(i).outFile.getPath()));
|
|
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
|
|
}
|
|
|
|
//Remove thread
|
|
threads.remove(i);
|
|
}
|
|
}
|
|
|
|
db.setTransactionSuccessful();
|
|
db.endTransaction();
|
|
|
|
//Create new download tasks
|
|
if (running) {
|
|
int nThreads = settings.downloadThreads - threads.size();
|
|
for (int i = 0; i<nThreads; i++) {
|
|
for (int j = 0; j < downloads.size(); j++) {
|
|
if (downloads.get(j).state == Download.DownloadState.NONE) {
|
|
//Update download
|
|
Download d = downloads.get(j);
|
|
d.state = Download.DownloadState.DOWNLOADING;
|
|
downloads.set(j, d);
|
|
|
|
//Create thread
|
|
DownloadThread thread = new DownloadThread(d);
|
|
thread.start();
|
|
threads.add(thread);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//Check if last download
|
|
if (threads.size() == 0) {
|
|
running = false;
|
|
}
|
|
}
|
|
//Send updates to UI
|
|
updateProgress();
|
|
updateState();
|
|
}
|
|
|
|
//Send state change to UI
|
|
private void updateState() {
|
|
Bundle b = new Bundle();
|
|
b.putBoolean("running", running);
|
|
//Get count of not downloaded tracks
|
|
int queueSize = 0;
|
|
for (int i=0; i<downloads.size(); i++) {
|
|
if (downloads.get(i).state == Download.DownloadState.NONE)
|
|
queueSize++;
|
|
}
|
|
b.putInt("queueSize", queueSize);
|
|
sendMessage(SERVICE_ON_STATE_CHANGE, b);
|
|
}
|
|
|
|
//Wrapper to prevent threads racing
|
|
private void updateQueueWrapper() {
|
|
updateRequests.add(true);
|
|
if (!updating) {
|
|
updating = true;
|
|
while (updateRequests.size() > 0) {
|
|
updateQueue();
|
|
//Because threading
|
|
if (updateRequests.size() > 0)
|
|
updateRequests.remove(0);
|
|
}
|
|
}
|
|
updating = false;
|
|
}
|
|
|
|
//Loads downloads from database
|
|
private void loadDownloads() {
|
|
Cursor cursor = db.query("Downloads", null, null, null, null, null, null);
|
|
|
|
//Parse downloads
|
|
while (cursor.moveToNext()) {
|
|
|
|
//Duplicate check
|
|
int downloadId = cursor.getInt(0);
|
|
Download.DownloadState state = Download.DownloadState.values()[cursor.getInt(1)];
|
|
boolean skip = false;
|
|
for (int i=0; i<downloads.size(); i++) {
|
|
if (downloads.get(i).id == downloadId) {
|
|
if (downloads.get(i).state != state) {
|
|
//Different state, update state, only for finished/error
|
|
if (downloads.get(i).state.getValue() >= 3) {
|
|
downloads.set(i, Download.fromSQL(cursor));
|
|
}
|
|
}
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
//Add to queue
|
|
if (!skip)
|
|
downloads.add(Download.fromSQL(cursor));
|
|
}
|
|
cursor.close();
|
|
|
|
updateState();
|
|
}
|
|
|
|
//Stop downloads
|
|
private void stop() {
|
|
running = false;
|
|
for (int i=0; i<threads.size(); i++) {
|
|
threads.get(i).stopDownload();
|
|
}
|
|
updateState();
|
|
}
|
|
|
|
|
|
public class DownloadThread extends Thread {
|
|
|
|
Download download;
|
|
File parentDir;
|
|
File outFile;
|
|
JSONObject trackJson;
|
|
JSONObject albumJson;
|
|
JSONObject privateJson;
|
|
JSONObject lyricsData = null;
|
|
boolean stopDownload = false;
|
|
DownloadThread(Download download) {
|
|
this.download = download;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
//Set state
|
|
download.state = Download.DownloadState.DOWNLOADING;
|
|
|
|
//Authorize deezer api
|
|
if (!deezer.authorized && !deezer.authorizing)
|
|
deezer.authorize();
|
|
|
|
while (deezer.authorizing)
|
|
try {Thread.sleep(50);} catch (Exception ignored) {}
|
|
|
|
//Don't fetch meta if user uploaded mp3
|
|
if (!download.isUserUploaded()) {
|
|
try {
|
|
JSONObject privateRaw = deezer.callGWAPI("deezer.pageTrack", "{\"sng_id\": \"" + download.trackId + "\"}");
|
|
privateJson = privateRaw.getJSONObject("results").getJSONObject("DATA");
|
|
if (privateRaw.getJSONObject("results").has("LYRICS")) {
|
|
lyricsData = privateRaw.getJSONObject("results").getJSONObject("LYRICS");
|
|
}
|
|
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
|
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
|
e.printStackTrace();
|
|
download.state = Download.DownloadState.ERROR;
|
|
exit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Fallback
|
|
Deezer.QualityInfo qualityInfo = new Deezer.QualityInfo(this.download.quality, this.download.trackId, this.download.md5origin, this.download.mediaVersion, logger);
|
|
if (!download.isUserUploaded()) {
|
|
try {
|
|
boolean res = qualityInfo.fallback(deezer);
|
|
if (!res)
|
|
throw new Exception("No more to fallback!");
|
|
|
|
download.quality = qualityInfo.quality;
|
|
} catch (Exception e) {
|
|
logger.error("Fallback failed " + e.toString());
|
|
download.state = Download.DownloadState.DEEZER_ERROR;
|
|
exit();
|
|
return;
|
|
}
|
|
} else {
|
|
//User uploaded MP3
|
|
qualityInfo.quality = 3;
|
|
}
|
|
|
|
if (!download.priv) {
|
|
//Check file
|
|
try {
|
|
if (download.isUserUploaded()) {
|
|
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, download.title));
|
|
} else {
|
|
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, qualityInfo.quality));
|
|
}
|
|
parentDir = new File(outFile.getParent());
|
|
} catch (Exception e) {
|
|
logger.error("Error generating track filename (" + download.path + "): " + e.toString(), download);
|
|
e.printStackTrace();
|
|
download.state = Download.DownloadState.ERROR;
|
|
exit();
|
|
return;
|
|
}
|
|
} else {
|
|
//Private track
|
|
outFile = new File(download.path);
|
|
parentDir = new File(outFile.getParent());
|
|
}
|
|
//File already exists
|
|
if (outFile.exists()) {
|
|
//Delete if overwriting enabled
|
|
if (settings.overwriteDownload) {
|
|
outFile.delete();
|
|
} else {
|
|
download.state = Download.DownloadState.DONE;
|
|
exit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Temporary encrypted file
|
|
File tmpFile = new File(getCacheDir(), download.id + ".ENC");
|
|
//Get start bytes offset
|
|
long start = 0;
|
|
if (tmpFile.exists()) {
|
|
start = tmpFile.length();
|
|
}
|
|
|
|
//Download
|
|
String sURL = Deezer.getTrackUrl(qualityInfo.trackId, qualityInfo.md5origin, qualityInfo.mediaVersion, qualityInfo.quality);
|
|
try {
|
|
URL url = new URL(sURL);
|
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
|
//Set headers
|
|
connection.setConnectTimeout(30000);
|
|
connection.setRequestMethod("GET");
|
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
|
connection.setRequestProperty("Accept-Language", "*");
|
|
connection.setRequestProperty("Accept", "*/*");
|
|
connection.setRequestProperty("Range", "bytes=" + start + "-");
|
|
connection.connect();
|
|
|
|
//Open streams
|
|
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
|
OutputStream outputStream = new FileOutputStream(tmpFile.getPath(), true);
|
|
//Save total
|
|
download.filesize = start + connection.getContentLength();
|
|
//Download
|
|
byte[] buffer = new byte[4096];
|
|
long received = 0;
|
|
int read;
|
|
while ((read = inputStream.read(buffer, 0, 4096)) != -1) {
|
|
outputStream.write(buffer, 0, read);
|
|
received += read;
|
|
download.received = start + received;
|
|
|
|
//Stop/Cancel download
|
|
if (stopDownload) {
|
|
download.state = Download.DownloadState.NONE;
|
|
try {
|
|
inputStream.close();
|
|
outputStream.close();
|
|
connection.disconnect();
|
|
} catch (Exception ignored) {}
|
|
exit();
|
|
return;
|
|
}
|
|
}
|
|
//On done
|
|
inputStream.close();
|
|
outputStream.close();
|
|
connection.disconnect();
|
|
//Update
|
|
download.state = Download.DownloadState.POST;
|
|
updateProgress();
|
|
} catch (Exception e) {
|
|
//Download error
|
|
logger.error("Download error: " + e.toString(), download);
|
|
e.printStackTrace();
|
|
download.state = Download.DownloadState.ERROR;
|
|
exit();
|
|
return;
|
|
}
|
|
|
|
//Post processing
|
|
|
|
//Decrypt
|
|
try {
|
|
File decFile = new File(tmpFile.getPath() + ".DEC");
|
|
deezer.decryptFile(download.trackId, tmpFile.getPath(), decFile.getPath());
|
|
tmpFile.delete();
|
|
tmpFile = decFile;
|
|
} catch (Exception e) {
|
|
logger.error("Decryption error: " + e.toString(), download);
|
|
e.printStackTrace();
|
|
//Shouldn't ever fail
|
|
}
|
|
|
|
//If exists (duplicate download in DB), don't overwrite.
|
|
if (outFile.exists()) {
|
|
download.state = Download.DownloadState.DONE;
|
|
exit();
|
|
return;
|
|
}
|
|
|
|
//Create dirs and copy
|
|
parentDir.mkdirs();
|
|
if (!tmpFile.renameTo(outFile)) {
|
|
try {
|
|
//Copy file
|
|
FileInputStream inputStream = new FileInputStream(tmpFile);
|
|
FileOutputStream outputStream = new FileOutputStream(outFile);
|
|
FileChannel inputChannel = inputStream.getChannel();
|
|
FileChannel outputChannel = outputStream.getChannel();
|
|
inputChannel.transferTo(0, inputChannel.size(), outputChannel);
|
|
inputStream.close();
|
|
outputStream.close();
|
|
//Delete temp
|
|
tmpFile.delete();
|
|
} catch (Exception e) {
|
|
//Clean
|
|
try {
|
|
outFile.delete();
|
|
tmpFile.delete();
|
|
} catch (Exception ignored) {}
|
|
//Log & Exit
|
|
logger.error("Error moving file! " + outFile.getPath() + ", " + e.toString(), download);
|
|
e.printStackTrace();
|
|
download.state = Download.DownloadState.ERROR;
|
|
exit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Cover & Tags, ignore on user uploaded
|
|
if (!download.priv && !download.isUserUploaded()) {
|
|
|
|
//Download cover for each track
|
|
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
|
|
|
try {
|
|
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/" + Integer.toString(settings.albumArtResolution) + "x" + Integer.toString(settings.albumArtResolution) + "-000000-80-0-0.jpg");
|
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
//Set headers
|
|
connection.setRequestMethod("GET");
|
|
connection.connect();
|
|
//Open streams
|
|
InputStream inputStream = connection.getInputStream();
|
|
OutputStream outputStream = new FileOutputStream(coverFile.getPath());
|
|
//Download
|
|
byte[] buffer = new byte[4096];
|
|
int read = 0;
|
|
while ((read = inputStream.read(buffer)) != -1) {
|
|
outputStream.write(buffer, 0, read);
|
|
}
|
|
//On done
|
|
try {
|
|
inputStream.close();
|
|
outputStream.close();
|
|
connection.disconnect();
|
|
} catch (Exception ignored) {}
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Error downloading cover! " + e.toString(), download);
|
|
e.printStackTrace();
|
|
}
|
|
|
|
//Lyrics
|
|
if (lyricsData != null) {
|
|
if (settings.downloadLyrics) {
|
|
try {
|
|
String lrcData = Deezer.generateLRC(lyricsData, trackJson);
|
|
//Create file
|
|
String lrcFilename = outFile.getPath().substring(0, outFile.getPath().lastIndexOf(".") + 1) + "lrc";
|
|
FileOutputStream fileOutputStream = new FileOutputStream(lrcFilename);
|
|
fileOutputStream.write(lrcData.getBytes());
|
|
fileOutputStream.close();
|
|
|
|
} catch (Exception e) {
|
|
logger.warn("Error downloading lyrics! " + e.toString(), download);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Tag
|
|
try {
|
|
deezer.tagTrack(outFile.getPath(), trackJson, albumJson, coverFile.getPath(), lyricsData, privateJson, settings);
|
|
} catch (Exception e) {
|
|
Log.e("ERR", "Tagging error!");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
//Delete cover if disabled
|
|
if (!settings.trackCover)
|
|
coverFile.delete();
|
|
|
|
//Album cover
|
|
if (settings.albumCover)
|
|
downloadAlbumCover(albumJson);
|
|
}
|
|
|
|
download.state = Download.DownloadState.DONE;
|
|
//Queue update
|
|
updateQueueWrapper();
|
|
stopSelf();
|
|
}
|
|
|
|
//Each track has own album art, this is to download cover.jpg
|
|
void downloadAlbumCover(JSONObject albumJson) {
|
|
//Checks
|
|
if (albumJson == null || !albumJson.has("md5_image")) return;
|
|
File coverFile = new File(parentDir, "cover.jpg");
|
|
if (coverFile.exists()) return;
|
|
//Don't download if doesn't have album
|
|
if (!download.path.matches(".*/.*%album%.*/.*")) return;
|
|
|
|
try {
|
|
//Create to lock
|
|
coverFile.createNewFile();
|
|
|
|
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/" + Integer.toString(settings.albumArtResolution) + "x" + Integer.toString(settings.albumArtResolution) + "-000000-80-0-0.jpg");
|
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
//Set headers
|
|
connection.setRequestMethod("GET");
|
|
connection.connect();
|
|
//Open streams
|
|
InputStream inputStream = connection.getInputStream();
|
|
OutputStream outputStream = new FileOutputStream(coverFile.getPath());
|
|
//Download
|
|
byte[] buffer = new byte[4096];
|
|
int read = 0;
|
|
while ((read = inputStream.read(buffer)) != -1) {
|
|
outputStream.write(buffer, 0, read);
|
|
}
|
|
//On done
|
|
try {
|
|
inputStream.close();
|
|
outputStream.close();
|
|
connection.disconnect();
|
|
} catch (Exception ignored) {}
|
|
//Create .nomedia to not spam gallery
|
|
if (settings.nomediaFiles)
|
|
new File(parentDir, ".nomedia").createNewFile();
|
|
} catch (Exception e) {
|
|
logger.warn("Error downloading album cover! " + e.toString(), download);
|
|
coverFile.delete();
|
|
}
|
|
}
|
|
|
|
void stopDownload() {
|
|
stopDownload = true;
|
|
}
|
|
|
|
//Clean stop/exit
|
|
private void exit() {
|
|
updateQueueWrapper();
|
|
stopSelf();
|
|
}
|
|
|
|
}
|
|
|
|
//500ms loop to update notifications and UI
|
|
private void createProgressUpdateHandler() {
|
|
progressUpdateHandler.postDelayed(() -> {
|
|
updateProgress();
|
|
createProgressUpdateHandler();
|
|
}, 500);
|
|
}
|
|
|
|
//Updates notification and UI
|
|
private void updateProgress() {
|
|
if (threads.size() > 0) {
|
|
//Convert threads to bundles, send to activity;
|
|
Bundle b = new Bundle();
|
|
ArrayList<Bundle> down = new ArrayList<>();
|
|
for (int i=0; i<threads.size(); i++) {
|
|
//Create bundle
|
|
Download download = threads.get(i).download;
|
|
down.add(createProgressBundle(download));
|
|
//Notification
|
|
updateNotification(download);
|
|
}
|
|
b.putParcelableArrayList("downloads", down);
|
|
sendMessage(SERVICE_ON_PROGRESS, b);
|
|
}
|
|
}
|
|
|
|
//Create bundle with download progress & state
|
|
private Bundle createProgressBundle(Download download) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("id", download.id);
|
|
bundle.putLong("received", download.received);
|
|
bundle.putLong("filesize", download.filesize);
|
|
bundle.putInt("quality", download.quality);
|
|
bundle.putInt("state", download.state.getValue());
|
|
return bundle;
|
|
}
|
|
|
|
private void updateNotification(Download download) {
|
|
//Cancel notification for done/none/error downloads
|
|
if (download.state == Download.DownloadState.NONE || download.state.getValue() >= 3) {
|
|
notificationManager.cancel(NOTIFICATION_ID_START + download.id);
|
|
return;
|
|
}
|
|
|
|
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, DownloadService.NOTIFICATION_CHANNEL_ID)
|
|
.setContentTitle(download.title)
|
|
.setSmallIcon(R.drawable.ic_logo)
|
|
.setPriority(NotificationCompat.PRIORITY_MIN);
|
|
|
|
//Show progress when downloading
|
|
if (download.state == Download.DownloadState.DOWNLOADING) {
|
|
if (download.filesize <= 0) download.filesize = 1;
|
|
notificationBuilder.setContentText(String.format("%s / %s", formatFilesize(download.received), formatFilesize(download.filesize)));
|
|
notificationBuilder.setProgress(100, (int)((download.received / (float)download.filesize)*100), false);
|
|
}
|
|
|
|
//Indeterminate on PostProcess
|
|
if (download.state == Download.DownloadState.POST) {
|
|
//TODO: Use strings
|
|
notificationBuilder.setContentText("Post processing...");
|
|
notificationBuilder.setProgress(1, 1, true);
|
|
}
|
|
|
|
notificationManager.notify(NOTIFICATION_ID_START + download.id, notificationBuilder.build());
|
|
}
|
|
|
|
//https://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
|
|
public static String formatFilesize(long size) {
|
|
if(size <= 0) return "0B";
|
|
final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
|
|
int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
|
|
return new DecimalFormat("#,##0.##").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
|
|
}
|
|
|
|
//Handler for incoming messages
|
|
class IncomingHandler extends Handler {
|
|
IncomingHandler(Context context) {
|
|
context.getApplicationContext();
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
//Load downloads from DB
|
|
case SERVICE_LOAD_DOWNLOADS:
|
|
loadDownloads();
|
|
break;
|
|
|
|
//Start/Resume
|
|
case SERVICE_START_DOWNLOAD:
|
|
running = true;
|
|
if (downloads.size() == 0)
|
|
loadDownloads();
|
|
updateQueue();
|
|
updateState();
|
|
break;
|
|
|
|
//Load settings
|
|
case SERVICE_SETTINGS_UPDATE:
|
|
settings = DownloadSettings.fromBundle(msg.getData());
|
|
deezer.arl = settings.arl;
|
|
break;
|
|
|
|
//Stop downloads
|
|
case SERVICE_STOP_DOWNLOADS:
|
|
stop();
|
|
break;
|
|
|
|
//Remove download
|
|
case SERVICE_REMOVE_DOWNLOAD:
|
|
int downloadId = msg.getData().getInt("id");
|
|
for (int i=0; i<downloads.size(); i++) {
|
|
Download d = downloads.get(i);
|
|
//Only remove if not downloading
|
|
if (d.id == downloadId) {
|
|
if (d.state == Download.DownloadState.DOWNLOADING || d.state == Download.DownloadState.POST) {
|
|
return;
|
|
}
|
|
downloads.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
//Remove from DB
|
|
db.delete("Downloads", "id == ?", new String[]{Integer.toString(downloadId)});
|
|
updateState();
|
|
break;
|
|
|
|
//Retry failed downloads
|
|
case SERVICE_RETRY_DOWNLOADS:
|
|
db.beginTransaction();
|
|
for (int i=0; i<downloads.size(); i++) {
|
|
Download d = downloads.get(i);
|
|
if (d.state == Download.DownloadState.DEEZER_ERROR || d.state == Download.DownloadState.ERROR) {
|
|
//Retry only failed
|
|
d.state = Download.DownloadState.NONE;
|
|
downloads.set(i, d);
|
|
//Update DB
|
|
ContentValues values = new ContentValues();
|
|
values.put("state", 0);
|
|
db.update("Downloads", values, "id == ?", new String[]{Integer.toString(d.id)});
|
|
}
|
|
}
|
|
db.setTransactionSuccessful();
|
|
db.endTransaction();
|
|
updateState();
|
|
break;
|
|
|
|
//Remove downloads by state
|
|
case SERVICE_REMOVE_DOWNLOADS:
|
|
//Don't remove currently downloading, user has to stop first
|
|
Download.DownloadState state = Download.DownloadState.values()[msg.getData().getInt("state")];
|
|
if (state == Download.DownloadState.DOWNLOADING || state == Download.DownloadState.POST) return;
|
|
|
|
db.beginTransaction();
|
|
int i = (downloads.size() - 1);
|
|
while (i >= 0) {
|
|
Download d = downloads.get(i);
|
|
if (d.state == state) {
|
|
//Remove
|
|
db.delete("Downloads", "id == ?", new String[]{Integer.toString(d.id)});
|
|
downloads.remove(i);
|
|
}
|
|
i--;
|
|
}
|
|
//Delete from DB, done downloads after app restart aren't in downloads array
|
|
db.delete("Downloads", "state == ?", new String[]{Integer.toString(msg.getData().getInt("state"))});
|
|
//Save
|
|
db.setTransactionSuccessful();
|
|
db.endTransaction();
|
|
updateState();
|
|
break;
|
|
|
|
default:
|
|
super.handleMessage(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Send message to MainActivity
|
|
void sendMessage(int type, Bundle data) {
|
|
if (serviceMessenger != null) {
|
|
Message msg = Message.obtain(null, type);
|
|
msg.setData(data);
|
|
try {
|
|
activityMessenger.send(msg);
|
|
} catch (RemoteException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
static class DownloadSettings {
|
|
|
|
int downloadThreads;
|
|
boolean overwriteDownload;
|
|
boolean downloadLyrics;
|
|
boolean trackCover;
|
|
String arl;
|
|
boolean albumCover;
|
|
boolean nomediaFiles;
|
|
String artistSeparator;
|
|
int albumArtResolution;
|
|
|
|
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover, String arl, boolean albumCover, boolean nomediaFiles, String artistSeparator, int albumArtResolution) {
|
|
this.downloadThreads = downloadThreads;
|
|
this.overwriteDownload = overwriteDownload;
|
|
this.downloadLyrics = downloadLyrics;
|
|
this.trackCover = trackCover;
|
|
this.arl = arl;
|
|
this.albumCover = albumCover;
|
|
this.nomediaFiles = nomediaFiles;
|
|
this.artistSeparator = artistSeparator;
|
|
this.albumArtResolution = albumArtResolution;
|
|
}
|
|
|
|
//Parse settings from bundle sent from UI
|
|
static DownloadSettings fromBundle(Bundle b) {
|
|
JSONObject json;
|
|
try {
|
|
json = new JSONObject(b.getString("json"));
|
|
return new DownloadSettings(
|
|
json.getInt("downloadThreads"),
|
|
json.getBoolean("overwriteDownload"),
|
|
json.getBoolean("downloadLyrics"),
|
|
json.getBoolean("trackCover"),
|
|
json.getString("arl"),
|
|
json.getBoolean("albumCover"),
|
|
json.getBoolean("nomediaFiles"),
|
|
json.getString("artistSeparator"),
|
|
json.getInt("albumArtResolution")
|
|
);
|
|
} catch (Exception e) {
|
|
//Shouldn't happen
|
|
Log.e("ERR", "Error loading settings!");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|