2020-10-09 20:52:45 +02:00
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 ;
2021-02-09 21:14:14 +01:00
import org.json.JSONArray ;
2020-10-09 20:52:45 +02:00
import org.json.JSONObject ;
import java.io.BufferedInputStream ;
import java.io.File ;
2020-10-11 22:06:29 +02:00
import java.io.FileInputStream ;
2020-10-09 20:52:45 +02:00
import java.io.FileOutputStream ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.net.HttpURLConnection ;
import java.net.URL ;
2020-10-11 22:06:29 +02:00
import java.nio.channels.FileChannel ;
2020-10-10 22:51:20 +02:00
import java.nio.file.Files ;
import java.nio.file.StandardCopyOption ;
2020-10-09 20:52:45 +02:00
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 ;
2020-10-10 22:51:20 +02:00
Deezer deezer = new Deezer ( ) ;
2020-10-09 20:52:45 +02:00
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 ( ) ;
2020-10-10 22:51:20 +02:00
DownloadLog logger = new DownloadLog ( ) ;
2020-10-09 20:52:45 +02:00
2020-10-10 22:51:20 +02:00
public DownloadService ( ) { }
2020-10-09 20:52:45 +02:00
@Override
public void onCreate ( ) {
super . onCreate ( ) ;
//Setup notifications
context = this ;
notificationManager = NotificationManagerCompat . from ( context ) ;
createNotificationChannel ( ) ;
createProgressUpdateHandler ( ) ;
2020-10-10 22:51:20 +02:00
//Setup logger, deezer api
logger . open ( context ) ;
2020-10-11 22:06:29 +02:00
deezer . init ( logger , " " ) ;
2020-10-10 22:51:20 +02:00
2020-10-09 20:52:45 +02:00
//Get DB
DownloadsDatabase dbHelper = new DownloadsDatabase ( getApplicationContext ( ) ) ;
db = dbHelper . getWritableDatabase ( ) ;
}
@Override
public void onDestroy ( ) {
//Cancel notifications
notificationManager . cancelAll ( ) ;
2020-10-10 22:51:20 +02:00
//Logger
logger . close ( ) ;
2020-10-09 20:52:45 +02:00
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
2020-12-27 19:33:59 +01:00
if ( intent ! = null ) {
2020-10-09 20:52:45 +02:00
activityMessenger = intent . getParcelableExtra ( " activityMessenger " ) ;
2020-12-27 19:33:59 +01:00
}
2020-10-09 20:52:45 +02:00
2020-11-28 22:32:17 +01:00
//return super.onStartCommand(intent, flags, startId);
//Prevent battery savers I guess
return START_STICKY ;
2020-10-09 20:52:45 +02:00
}
//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 ;
}
}
2020-10-10 22:51:20 +02:00
//Send updates to UI
updateProgress ( ) ;
updateState ( ) ;
2020-10-09 20:52:45 +02:00
}
//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 ( ) ;
2020-10-11 22:06:29 +02:00
//Because threading
if ( updateRequests . size ( ) > 0 )
updateRequests . remove ( 0 ) ;
2020-10-09 20:52:45 +02:00
}
}
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 ;
2020-10-12 22:49:13 +02:00
JSONObject privateJson ;
2020-10-14 21:09:16 +02:00
JSONObject lyricsData = null ;
2020-10-09 20:52:45 +02:00
boolean stopDownload = false ;
DownloadThread ( Download download ) {
this . download = download ;
}
@Override
public void run ( ) {
//Set state
download . state = Download . DownloadState . DOWNLOADING ;
2020-10-11 22:06:29 +02:00
//Authorize deezer api
if ( ! deezer . authorized & & ! deezer . authorizing )
deezer . authorize ( ) ;
while ( deezer . authorizing )
try { Thread . sleep ( 50 ) ; } catch ( Exception ignored ) { }
2020-12-27 19:33:59 +01:00
//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 " ) ;
}
2020-10-12 22:49:13 +02:00
trackJson = Deezer . callPublicAPI ( " track " , download . trackId ) ;
albumJson = Deezer . callPublicAPI ( " album " , Integer . toString ( trackJson . getJSONObject ( " album " ) . getInt ( " id " ) ) ) ;
2020-12-27 19:33:59 +01:00
} catch ( Exception e ) {
logger . error ( " Unable to fetch track and album metadata! " + e . toString ( ) , download ) ;
e . printStackTrace ( ) ;
download . state = Download . DownloadState . ERROR ;
exit ( ) ;
return ;
2020-10-12 22:49:13 +02:00
}
2020-10-11 22:06:29 +02:00
}
2020-11-28 22:32:17 +01:00
//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! " ) ;
2020-10-17 21:20:26 +02:00
2020-11-28 22:32:17 +01:00
download . quality = qualityInfo . quality ;
} catch ( Exception e ) {
logger . error ( " Fallback failed " + e . toString ( ) ) ;
download . state = Download . DownloadState . DEEZER_ERROR ;
exit ( ) ;
2020-10-11 22:06:29 +02:00
return ;
}
2020-11-28 22:32:17 +01:00
} else {
//User uploaded MP3
qualityInfo . quality = 3 ;
2020-10-09 20:52:45 +02:00
}
if ( ! download . priv ) {
//Check file
try {
2020-10-12 22:49:13 +02:00
if ( download . isUserUploaded ( ) ) {
2020-12-27 19:33:59 +01:00
outFile = new File ( Deezer . generateUserUploadedMP3Filename ( download . path , download . title ) ) ;
2020-10-12 22:49:13 +02:00
} else {
2020-11-28 22:32:17 +01:00
outFile = new File ( Deezer . generateFilename ( download . path , trackJson , albumJson , qualityInfo . quality ) ) ;
2020-10-12 22:49:13 +02:00
}
2020-10-09 20:52:45 +02:00
parentDir = new File ( outFile . getParent ( ) ) ;
} catch ( Exception e ) {
2020-10-10 22:51:20 +02:00
logger . error ( " Error generating track filename ( " + download . path + " ): " + e . toString ( ) , download ) ;
2020-10-09 20:52:45 +02:00
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
2020-11-28 22:32:17 +01:00
String sURL = Deezer . getTrackUrl ( qualityInfo . trackId , qualityInfo . md5origin , qualityInfo . mediaVersion , qualityInfo . quality ) ;
2020-10-09 20:52:45 +02:00
try {
URL url = new URL ( sURL ) ;
HttpsURLConnection connection = ( HttpsURLConnection ) url . openConnection ( ) ;
//Set headers
connection . setConnectTimeout ( 30000 ) ;
connection . setRequestMethod ( " GET " ) ;
2020-10-14 21:09:16 +02:00
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 " , " */* " ) ;
2020-10-09 20:52:45 +02:00
connection . setRequestProperty ( " Range " , " bytes= " + start + " - " ) ;
connection . connect ( ) ;
//Open streams
BufferedInputStream inputStream = new BufferedInputStream ( connection . getInputStream ( ) ) ;
2020-10-10 22:51:20 +02:00
OutputStream outputStream = new FileOutputStream ( tmpFile . getPath ( ) , true ) ;
2020-10-09 20:52:45 +02:00
//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
2020-10-10 22:51:20 +02:00
logger . error ( " Download error: " + e . toString ( ) , download ) ;
2020-10-09 20:52:45 +02:00
e . printStackTrace ( ) ;
download . state = Download . DownloadState . ERROR ;
exit ( ) ;
return ;
}
//Post processing
//If exists (duplicate download in DB), don't overwrite.
if ( outFile . exists ( ) ) {
download . state = Download . DownloadState . DONE ;
exit ( ) ;
return ;
}
2020-10-10 22:51:20 +02:00
//Create dirs and copy
parentDir . mkdirs ( ) ;
2020-10-09 20:52:45 +02:00
if ( ! tmpFile . renameTo ( outFile ) ) {
2020-10-10 22:51:20 +02:00
try {
2020-10-14 21:09:16 +02:00
//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 ( ) ;
2020-10-10 22:51:20 +02:00
} catch ( Exception e ) {
2020-10-11 22:06:29 +02:00
//Clean
try {
outFile . delete ( ) ;
tmpFile . delete ( ) ;
} catch ( Exception ignored ) { }
//Log & Exit
2020-10-10 22:51:20 +02:00
logger . error ( " Error moving file! " + outFile . getPath ( ) + " , " + e . toString ( ) , download ) ;
2020-10-11 22:06:29 +02:00
e . printStackTrace ( ) ;
2020-10-10 22:51:20 +02:00
download . state = Download . DownloadState . ERROR ;
exit ( ) ;
return ;
}
2020-10-09 20:52:45 +02:00
}
2020-10-12 22:49:13 +02:00
//Cover & Tags, ignore on user uploaded
if ( ! download . priv & & ! download . isUserUploaded ( ) ) {
2020-10-09 20:52:45 +02:00
2020-10-10 22:51:20 +02:00
//Download cover for each track
File coverFile = new File ( outFile . getPath ( ) . substring ( 0 , outFile . getPath ( ) . lastIndexOf ( '.' ) ) + " .jpg " ) ;
try {
2020-12-14 18:29:28 +01:00
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 " ) ;
2020-10-10 22:51:20 +02:00
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
2020-10-09 20:52:45 +02:00
try {
inputStream . close ( ) ;
outputStream . close ( ) ;
connection . disconnect ( ) ;
2020-10-10 22:51:20 +02:00
} catch ( Exception ignored ) { }
} catch ( Exception e ) {
logger . error ( " Error downloading cover! " + e . toString ( ) , download ) ;
e . printStackTrace ( ) ;
2020-10-11 22:06:29 +02:00
}
//Lyrics
2020-10-14 21:09:16 +02:00
if ( lyricsData ! = null ) {
2020-10-12 22:49:13 +02:00
if ( settings . downloadLyrics ) {
2020-10-14 21:09:16 +02:00
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 ) ;
}
2020-10-11 22:06:29 +02:00
}
2020-10-09 20:52:45 +02:00
}
//Tag
try {
2020-12-04 18:02:50 +01:00
deezer . tagTrack ( outFile . getPath ( ) , trackJson , albumJson , coverFile . getPath ( ) , lyricsData , privateJson , settings ) ;
2020-10-09 20:52:45 +02:00
} catch ( Exception e ) {
Log . e ( " ERR " , " Tagging error! " ) ;
e . printStackTrace ( ) ;
}
2020-10-10 22:51:20 +02:00
//Delete cover if disabled
if ( ! settings . trackCover )
coverFile . delete ( ) ;
//Album cover
2020-10-14 21:09:16 +02:00
if ( settings . albumCover )
downloadAlbumCover ( albumJson ) ;
2020-10-09 20:52:45 +02:00
}
download . state = Download . DownloadState . DONE ;
//Queue update
updateQueueWrapper ( ) ;
stopSelf ( ) ;
}
2020-10-10 22:51:20 +02:00
//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
2020-10-19 21:28:45 +02:00
if ( ! download . path . matches ( " .*/.*%album%.*/.* " ) ) return ;
2020-10-10 22:51:20 +02:00
try {
//Create to lock
coverFile . createNewFile ( ) ;
2020-12-14 18:29:28 +01:00
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 " ) ;
2020-10-10 22:51:20 +02:00
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 ) { }
2020-10-14 21:09:16 +02:00
//Create .nomedia to not spam gallery
2020-10-15 20:37:36 +02:00
if ( settings . nomediaFiles )
new File ( parentDir , " .nomedia " ) . createNewFile ( ) ;
2020-10-10 22:51:20 +02:00
} catch ( Exception e ) {
logger . warn ( " Error downloading album cover! " + e . toString ( ) , download ) ;
coverFile . delete ( ) ;
}
}
2020-10-09 20:52:45 +02:00
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 ;
2020-12-27 19:33:59 +01:00
if ( downloads . size ( ) = = 0 )
loadDownloads ( ) ;
2020-10-09 20:52:45 +02:00
updateQueue ( ) ;
updateState ( ) ;
break ;
//Load settings
case SERVICE_SETTINGS_UPDATE :
settings = DownloadSettings . fromBundle ( msg . getData ( ) ) ;
2020-10-11 22:06:29 +02:00
deezer . arl = settings . arl ;
2020-10-09 20:52:45 +02:00
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 ;
2020-10-10 22:51:20 +02:00
boolean trackCover ;
2020-10-11 22:06:29 +02:00
String arl ;
2020-10-14 21:09:16 +02:00
boolean albumCover ;
2020-10-15 20:37:36 +02:00
boolean nomediaFiles ;
2020-12-04 18:02:50 +01:00
String artistSeparator ;
2020-12-14 18:29:28 +01:00
int albumArtResolution ;
2021-02-09 21:14:14 +01:00
SelectedTags tags ;
2020-10-09 20:52:45 +02:00
2021-02-09 21:14:14 +01:00
private DownloadSettings ( int downloadThreads , boolean overwriteDownload , boolean downloadLyrics , boolean trackCover , String arl , boolean albumCover , boolean nomediaFiles , String artistSeparator , int albumArtResolution , SelectedTags tags ) {
2020-10-09 20:52:45 +02:00
this . downloadThreads = downloadThreads ;
this . overwriteDownload = overwriteDownload ;
this . downloadLyrics = downloadLyrics ;
2020-10-10 22:51:20 +02:00
this . trackCover = trackCover ;
2020-10-11 22:06:29 +02:00
this . arl = arl ;
2020-10-14 21:09:16 +02:00
this . albumCover = albumCover ;
2020-10-15 20:37:36 +02:00
this . nomediaFiles = nomediaFiles ;
2020-12-04 18:02:50 +01:00
this . artistSeparator = artistSeparator ;
2020-12-14 18:29:28 +01:00
this . albumArtResolution = albumArtResolution ;
2021-02-09 21:14:14 +01:00
this . tags = tags ;
2020-10-09 20:52:45 +02:00
}
//Parse settings from bundle sent from UI
static DownloadSettings fromBundle ( Bundle b ) {
2020-11-28 22:32:17 +01:00
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 " ) ,
2020-12-04 18:02:50 +01:00
json . getBoolean ( " nomediaFiles " ) ,
2020-12-14 18:29:28 +01:00
json . getString ( " artistSeparator " ) ,
2021-02-09 21:14:14 +01:00
json . getInt ( " albumArtResolution " ) ,
new SelectedTags ( json . getJSONArray ( " tags " ) )
2020-11-28 22:32:17 +01:00
) ;
} catch ( Exception e ) {
//Shouldn't happen
Log . e ( " ERR " , " Error loading settings! " ) ;
return null ;
}
2020-10-09 20:52:45 +02:00
}
}
2021-02-09 21:14:14 +01:00
static class SelectedTags {
boolean title = false ;
boolean album = false ;
boolean artist = false ;
boolean track = false ;
boolean disc = false ;
boolean albumArtist = false ;
boolean date = false ;
boolean label = false ;
boolean isrc = false ;
boolean upc = false ;
boolean trackTotal = false ;
boolean bpm = false ;
boolean lyrics = false ;
boolean genre = false ;
boolean contributors = false ;
boolean albumArt = false ;
SelectedTags ( JSONArray json ) {
//Array of tags, check if exist
try {
for ( int i = 0 ; i < json . length ( ) ; i + + ) {
switch ( json . getString ( i ) ) {
case " title " :
title = true ; break ;
case " album " :
album = true ; break ;
case " artist " :
artist = true ; break ;
case " track " :
track = true ; break ;
case " disc " :
disc = true ; break ;
case " albumArtist " :
albumArtist = true ; break ;
case " date " :
date = true ; break ;
case " label " :
label = true ; break ;
case " isrc " :
isrc = true ; break ;
case " upc " :
upc = true ; break ;
case " trackTotal " :
trackTotal = true ; break ;
case " bpm " :
bpm = true ; break ;
case " lyrics " :
lyrics = true ; break ;
case " genre " :
genre = true ; break ;
case " contributors " :
contributors = true ; break ;
case " art " :
albumArt = true ; break ;
}
}
} catch ( Exception e ) { }
}
}
2020-10-09 20:52:45 +02:00
}