0.5.1 - Download fixes
This commit is contained in:
parent
8db1223805
commit
22ceca2d9c
16 changed files with 437 additions and 91 deletions
|
@ -1,5 +1,6 @@
|
|||
package f.f.freezer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
|
@ -36,6 +37,13 @@ import javax.net.ssl.HttpsURLConnection;
|
|||
|
||||
public class Deezer {
|
||||
|
||||
DownloadLog logger;
|
||||
|
||||
//Initialize for logging
|
||||
void init(DownloadLog logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
//Get guest SID cookie from deezer.com
|
||||
public static String getSidCookie() throws Exception {
|
||||
URL url = new URL("https://deezer.com/");
|
||||
|
@ -102,7 +110,7 @@ public class Deezer {
|
|||
return out;
|
||||
}
|
||||
|
||||
public static int qualityFallback(String trackId, String md5origin, String mediaVersion, int originalQuality) throws Exception {
|
||||
public int qualityFallback(String trackId, String md5origin, String mediaVersion, int originalQuality) throws Exception {
|
||||
//Create HEAD requests to check if exists
|
||||
URL url = new URL(getTrackUrl(trackId, md5origin, mediaVersion, originalQuality));
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
|
@ -110,6 +118,7 @@ public class Deezer {
|
|||
int rc = connection.getResponseCode();
|
||||
//Track not available
|
||||
if (rc > 400) {
|
||||
logger.warn("Quality fallback, response code: " + Integer.toString(rc) + ", current: " + Integer.toString(originalQuality));
|
||||
//Returns -1 if no quality available
|
||||
if (originalQuality == 1) return -1;
|
||||
if (originalQuality == 3) return qualityFallback(trackId, md5origin, mediaVersion, 1);
|
||||
|
@ -251,6 +260,7 @@ public class Deezer {
|
|||
original = original.replaceAll("%0trackNumber%", String.format("%02d", trackNumber));
|
||||
//Year
|
||||
original = original.replaceAll("%year%", publicTrack.getString("release_date").substring(0, 4));
|
||||
original = original.replaceAll("%date%", publicTrack.getString("release_date"));
|
||||
|
||||
if (newQuality == 9) return original + ".flac";
|
||||
return original + ".mp3";
|
||||
|
|
99
android/app/src/main/java/f/f/freezer/DownloadLog.java
Normal file
99
android/app/src/main/java/f/f/freezer/DownloadLog.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
package f.f.freezer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class DownloadLog {
|
||||
|
||||
BufferedWriter writer;
|
||||
|
||||
//Open/Create file
|
||||
public void open(Context context) {
|
||||
File file = new File(context.getExternalFilesDir(""), "download.log");
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
writer = new BufferedWriter(new FileWriter(file, true));
|
||||
} catch (Exception ignored) {
|
||||
Log.e("DOWN", "Error opening download log!");
|
||||
}
|
||||
}
|
||||
|
||||
//Close log
|
||||
public void close() {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (Exception ignored) {
|
||||
Log.w("DOWN", "Error closing download log!");
|
||||
}
|
||||
}
|
||||
|
||||
public String time() {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
|
||||
return format.format(Calendar.getInstance().getTime());
|
||||
}
|
||||
|
||||
//Write error to log
|
||||
public void error(String info) {
|
||||
if (writer == null) return;
|
||||
String data = "E:" + time() + ": " + info;
|
||||
try {
|
||||
writer.write(data);
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
} catch (Exception ignored) {
|
||||
Log.w("DOWN", "Error writing into log.");
|
||||
}
|
||||
Log.e("DOWN", data);
|
||||
}
|
||||
|
||||
//Write error to log with download info
|
||||
public void error(String info, Download download) {
|
||||
if (writer == null) return;
|
||||
String data = "E:" + time() + " (TrackID: " + download.trackId + ", ID: " + Integer.toString(download.id) + "): " +info;
|
||||
try {
|
||||
writer.write(data);
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
} catch (Exception ignored) {
|
||||
Log.w("DOWN", "Error writing into log.");
|
||||
}
|
||||
Log.e("DOWN", data);
|
||||
}
|
||||
|
||||
//Write warning to log
|
||||
public void warn(String info) {
|
||||
if (writer == null) return;
|
||||
String data = "W:" + time() + ": " + info;
|
||||
try {
|
||||
writer.write(data);
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
} catch (Exception ignored) {
|
||||
Log.w("DOWN", "Error writing into log.");
|
||||
}
|
||||
Log.w("DOWN", data);
|
||||
}
|
||||
|
||||
//Write warning to log with download info
|
||||
public void warn(String info, Download download) {
|
||||
if (writer == null) return;
|
||||
String data = "W:" + time() + " (TrackID: " + download.trackId + ", ID: " + Integer.toString(download.id) + "): " +info;
|
||||
try {
|
||||
writer.write(data);
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
} catch (Exception ignored) {
|
||||
Log.w("DOWN", "Error writing into log.");
|
||||
}
|
||||
Log.w("DOWN", data);
|
||||
}
|
||||
|
||||
}
|
|
@ -30,6 +30,8 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
@ -54,6 +56,7 @@ public class DownloadService extends Service {
|
|||
DownloadSettings settings;
|
||||
Context context;
|
||||
SQLiteDatabase db;
|
||||
Deezer deezer = new Deezer();
|
||||
|
||||
Messenger serviceMessenger;
|
||||
Messenger activityMessenger;
|
||||
|
@ -62,12 +65,11 @@ public class DownloadService extends Service {
|
|||
ArrayList<Download> downloads = new ArrayList<>();
|
||||
ArrayList<DownloadThread> threads = new ArrayList<>();
|
||||
ArrayList<Boolean> updateRequests = new ArrayList<>();
|
||||
ArrayList<String> pendingCovers = new ArrayList<>();
|
||||
boolean updating = false;
|
||||
Handler progressUpdateHandler = new Handler();
|
||||
DownloadLog logger = new DownloadLog();
|
||||
|
||||
public DownloadService() {
|
||||
}
|
||||
public DownloadService() { }
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
@ -79,6 +81,10 @@ public class DownloadService extends Service {
|
|||
createNotificationChannel();
|
||||
createProgressUpdateHandler();
|
||||
|
||||
//Setup logger, deezer api
|
||||
logger.open(context);
|
||||
deezer.init(logger);
|
||||
|
||||
//Get DB
|
||||
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
||||
db = dbHelper.getWritableDatabase();
|
||||
|
@ -89,6 +95,9 @@ public class DownloadService extends Service {
|
|||
//Cancel notifications
|
||||
notificationManager.cancelAll();
|
||||
|
||||
//Logger
|
||||
logger.close();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
@ -178,10 +187,11 @@ public class DownloadService extends Service {
|
|||
//Check if last download
|
||||
if (threads.size() == 0) {
|
||||
running = false;
|
||||
updateState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Send updates to UI
|
||||
updateProgress();
|
||||
updateState();
|
||||
}
|
||||
|
||||
//Send state change to UI
|
||||
|
@ -273,15 +283,16 @@ public class DownloadService extends Service {
|
|||
//Quality fallback
|
||||
int newQuality;
|
||||
try {
|
||||
newQuality = Deezer.qualityFallback(download.trackId, download.md5origin, download.mediaVersion, download.quality);
|
||||
newQuality = deezer.qualityFallback(download.trackId, download.md5origin, download.mediaVersion, download.quality);
|
||||
} catch (Exception e) {
|
||||
Log.e("QF", "Quality fallback failed: " + e.toString());
|
||||
logger.error("Quality fallback failed: " + e.toString(), download);
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
//No quality available
|
||||
if (newQuality == -1) {
|
||||
logger.error("No available quality!", download);
|
||||
download.state = Download.DownloadState.DEEZER_ERROR;
|
||||
exit();
|
||||
return;
|
||||
|
@ -294,7 +305,7 @@ public class DownloadService extends Service {
|
|||
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
||||
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
||||
} catch (Exception e) {
|
||||
Log.e("ERR", "Unable to fetch track metadata.");
|
||||
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
|
@ -305,9 +316,8 @@ public class DownloadService extends Service {
|
|||
try {
|
||||
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
|
||||
parentDir = new File(outFile.getParent());
|
||||
parentDir.mkdirs();
|
||||
} catch (Exception e) {
|
||||
Log.e("ERR", "Error creating directories! TrackID: " + download.trackId);
|
||||
logger.error("Error generating track filename (" + download.path + "): " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
|
@ -351,7 +361,7 @@ public class DownloadService extends Service {
|
|||
|
||||
//Open streams
|
||||
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||
OutputStream outputStream = new FileOutputStream(tmpFile.getPath());
|
||||
OutputStream outputStream = new FileOutputStream(tmpFile.getPath(), true);
|
||||
//Save total
|
||||
download.filesize = start + connection.getContentLength();
|
||||
//Download
|
||||
|
@ -384,7 +394,7 @@ public class DownloadService extends Service {
|
|||
updateProgress();
|
||||
} catch (Exception e) {
|
||||
//Download error
|
||||
Log.e("DOWNLOAD", "Download error!");
|
||||
logger.error("Download error: " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
|
@ -397,7 +407,7 @@ public class DownloadService extends Service {
|
|||
try {
|
||||
Deezer.decryptTrack(tmpFile.getPath(), download.trackId);
|
||||
} catch (Exception e) {
|
||||
Log.e("DEC", "Decryption failed!");
|
||||
logger.error("Decryption error: " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
//Shouldn't ever fail
|
||||
}
|
||||
|
@ -408,54 +418,65 @@ public class DownloadService extends Service {
|
|||
exit();
|
||||
return;
|
||||
}
|
||||
//Copy to destination directory
|
||||
|
||||
//Create dirs and copy
|
||||
parentDir.mkdirs();
|
||||
if (!tmpFile.renameTo(outFile)) {
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
return;
|
||||
boolean error = true;
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Files.move(tmpFile.toPath(), outFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
tmpFile.delete();
|
||||
error = false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error moving file! " + outFile.getPath() + ", " + e.toString(), download);
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
logger.error("Error moving file! " + outFile.getPath(), download);
|
||||
download.state = Download.DownloadState.ERROR;
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!download.priv) {
|
||||
//Download cover
|
||||
File coverFile = new File(parentDir, "cover.jpg");
|
||||
//Wait for another thread to download it
|
||||
while (pendingCovers.contains(coverFile.getPath())) {
|
||||
try { Thread.sleep(100); } catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
if (!coverFile.exists()) {
|
||||
//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/" + trackJson.getString("md5_image") + "/1400x1400-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 {
|
||||
//Create fake file so other threads don't start downloading covers
|
||||
coverFile.createNewFile();
|
||||
pendingCovers.add(coverFile.getPath());
|
||||
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + trackJson.getString("md5_image") + "/1400x1400-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
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
Log.e("ERR", "Error downloading cover!");
|
||||
e.printStackTrace();
|
||||
coverFile.delete();
|
||||
}
|
||||
//Remove lock
|
||||
pendingCovers.remove(coverFile.getPath());
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error downloading cover! " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
coverFile.delete();
|
||||
}
|
||||
|
||||
|
||||
//Tag
|
||||
try {
|
||||
Deezer.tagTrack(outFile.getPath(), trackJson, albumJson, coverFile.getPath());
|
||||
|
@ -464,6 +485,13 @@ public class DownloadService extends Service {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//Delete cover if disabled
|
||||
if (!settings.trackCover)
|
||||
coverFile.delete();
|
||||
|
||||
//Album cover
|
||||
downloadAlbumCover(albumJson);
|
||||
|
||||
//Lyrics
|
||||
if (settings.downloadLyrics) {
|
||||
try {
|
||||
|
@ -475,7 +503,7 @@ public class DownloadService extends Service {
|
|||
fileOutputStream.write(lrcData.getBytes());
|
||||
fileOutputStream.close();
|
||||
} catch (Exception e) {
|
||||
Log.w("WAR", "Missing lyrics! " + e.toString());
|
||||
logger.warn("Error downloading lyrics! " + e.toString(), download);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -486,6 +514,46 @@ public class DownloadService extends Service {
|
|||
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.contains("/%album%/")) return;
|
||||
|
||||
try {
|
||||
//Create to lock
|
||||
coverFile.createNewFile();
|
||||
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/1400x1400-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.warn("Error downloading album cover! " + e.toString(), download);
|
||||
coverFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
void stopDownload() {
|
||||
stopDownload = true;
|
||||
}
|
||||
|
@ -691,16 +759,18 @@ public class DownloadService extends Service {
|
|||
int downloadThreads;
|
||||
boolean overwriteDownload;
|
||||
boolean downloadLyrics;
|
||||
boolean trackCover;
|
||||
|
||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics) {
|
||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover) {
|
||||
this.downloadThreads = downloadThreads;
|
||||
this.overwriteDownload = overwriteDownload;
|
||||
this.downloadLyrics = downloadLyrics;
|
||||
this.trackCover = trackCover;
|
||||
}
|
||||
|
||||
//Parse settings from bundle sent from UI
|
||||
static DownloadSettings fromBundle(Bundle b) {
|
||||
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"));
|
||||
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"), b.getBoolean("trackCover"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ public class MainActivity extends FlutterActivity {
|
|||
ArrayList<HashMap> downloads = call.arguments();
|
||||
for (int i=0; i<downloads.size(); i++) {
|
||||
//Check if exists
|
||||
Cursor cursor = db.rawQuery("SELECT id, state FROM Downloads WHERE trackId == ? AND path == ?",
|
||||
Cursor cursor = db.rawQuery("SELECT id, state, quality FROM Downloads WHERE trackId == ? AND path == ?",
|
||||
new String[]{(String)downloads.get(i).get("trackId"), (String)downloads.get(i).get("path")});
|
||||
if (cursor.getCount() > 0) {
|
||||
//If done or error, set state to NONE - they should be skipped because file exists
|
||||
|
@ -74,6 +74,7 @@ public class MainActivity extends FlutterActivity {
|
|||
if (cursor.getInt(1) >= 3) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("state", 0);
|
||||
values.put("quality", cursor.getInt(2));
|
||||
db.update("Downloads", values, "id == ?", new String[]{Integer.toString(cursor.getInt(0))});
|
||||
Log.d("INFO", "Already exists in DB, updating to none state!");
|
||||
} else {
|
||||
|
@ -116,6 +117,7 @@ public class MainActivity extends FlutterActivity {
|
|||
bundle.putInt("downloadThreads", (int)call.argument("downloadThreads"));
|
||||
bundle.putBoolean("overwriteDownload", (boolean)call.argument("overwriteDownload"));
|
||||
bundle.putBoolean("downloadLyrics", (boolean)call.argument("downloadLyrics"));
|
||||
bundle.putBoolean("trackCover", (boolean)call.argument("trackCover"));
|
||||
sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle);
|
||||
|
||||
result.success(null);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue