From ea48d929a409a1184d8cbdae7672a149072e52e6 Mon Sep 17 00:00:00 2001 From: shabinder Date: Fri, 14 May 2021 02:51:33 +0530 Subject: [PATCH] (WIP)Android SAF --- .../com/shabinder/spotiflyer/MainActivity.kt | 40 +-- .../com/shabinder/common/di/AndroidDir.kt | 277 ++++++------------ .../common/di/worker/ForegroundService.kt | 93 ++---- .../com/shabinder/common/di/worker/Utils.kt | 5 +- .../kotlin/com/shabinder/common/di/Dir.kt | 2 +- 5 files changed, 125 insertions(+), 292 deletions(-) diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index e9e70c0f..14323d76 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -46,7 +46,6 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponen import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.github.k1rakishou.fsaf.FileChooser -import com.github.k1rakishou.fsaf.FileManager import com.github.k1rakishou.fsaf.callback.directory.DirectoryChooserCallback import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsPadding @@ -58,7 +57,6 @@ import com.shabinder.common.models.Actions import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey -import com.shabinder.common.models.SpotiFlyerBaseDir import com.shabinder.common.models.Status import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.methods @@ -148,34 +146,13 @@ class MainActivity : ComponentActivity() { @Suppress("DEPRECATION") private fun setUpOnPrefClickListener() { - /*Get User Permission to access External SD*//* + /*Get User Permission to access External SD*/ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) } - startActivityForResult(intent, externalSDWriteAccess)*/ - val fileChooser = FileChooser(applicationContext) - fileChooser.openChooseDirectoryDialog(object : DirectoryChooserCallback() { - override fun onResult(uri: Uri) { - println("treeUri = $uri") - // Can be only used using SAF - contentResolver.takePersistableUriPermission(uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - val treeDocumentFile = DocumentFile.fromTreeUri(applicationContext, uri) - - dir.setDownloadDirectory(uri) - showPopUpMessage("New Download Directory Set") - GlobalScope.launch { - dir.createDirectories() - } - } - - override fun onCancel(reason: String) { - println("Canceled by user") - } - }) + startActivityForResult(intent, externalSDWriteAccess) } private fun showPopUpMessage(string: String, long: Boolean = false) { @@ -327,24 +304,19 @@ class MainActivity : ComponentActivity() { externalSDWriteAccess -> { // Can be only used using SAF - /*if (resultCode == RESULT_OK) { + if (resultCode == RESULT_OK) { val treeUri: Uri? = data?.data - if (treeUri == null){ + if (treeUri == null) { showPopUpMessage("Some Error Occurred While Setting New Download Directory") }else { // Persistently save READ & WRITE Access to whole Selected Directory Tree contentResolver.takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - dir.setDownloadDirectory(com.shabinder.common.models.File( - DocumentFile.fromTreeUri(applicationContext,treeUri)?.createDirectory("SpotiFlyer")!!) - ) + dir.setDownloadDirectory(treeUri) showPopUpMessage("New Download Directory Set") - GlobalScope.launch { - dir.createDirectories() - } } - }*/ + } } } } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt index e40ebd4a..d5512fe5 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt @@ -16,17 +16,16 @@ package com.shabinder.common.di +import android.content.ContentValues import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.os.Environment -import android.util.Log +import android.provider.MediaStore import androidx.compose.ui.graphics.asImageBitmap -import androidx.documentfile.provider.DocumentFile import co.touchlab.kermit.Kermit import com.github.k1rakishou.fsaf.FileManager -import com.github.k1rakishou.fsaf.file.AbstractFile import com.github.k1rakishou.fsaf.file.DirectorySegment import com.github.k1rakishou.fsaf.file.FileSegment import com.github.k1rakishou.fsaf.manager.base_directory.BaseDirectory @@ -45,13 +44,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.get -import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.net.HttpURLConnection import java.net.URL + /* * Ignore Deprecation * Deprecation is only a Suggestion P-) @@ -60,22 +59,32 @@ import java.net.URL actual class Dir actual constructor( private val logger: Kermit, private val settings: Settings, - private val spotiFlyerDatabase: SpotiFlyerDatabase, + spotiFlyerDatabase: SpotiFlyerDatabase, ): KoinComponent { private val context: Context = get() val fileManager = FileManager(context) init { - fileManager.registerBaseDir(SpotiFlyerBaseDir({ getDirType() }, - getJavaFile = { - java.io.File( - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString() - + "/SpotiFlyer/" - ) - }, - getSAFUri = { null } - )) + fileManager.apply { + registerBaseDir(SpotiFlyerBaseDir({ getDirType() }, + getJavaFile = { + java.io.File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + .toString() + + "/SpotiFlyer/" + ) + }, + getSAFUri = { + settings.getStringOrNull(DirKey)?.let { + Uri.parse(it) + } + } + )) + defaultDir().documentFile?.let { + createSnapshot(it,true) + } + } } companion object { @@ -105,27 +114,32 @@ actual class Dir actual constructor( actual fun setDownloadDirectory(newBasePath:File) = settings.putString( DirKey, - newBasePath.documentFile?.getFullPath()!! + newBasePath.documentFile!!.getFullPath() ) fun setDownloadDirectory(treeUri:Uri) { - fileManager.registerBaseDir(SpotiFlyerBaseDir( - { getDirType() }, - getJavaFile = { - null - }, - getSAFUri = { - treeUri + try { + fileManager.apply { + registerBaseDir(SpotiFlyerBaseDir( + { getDirType() }, + getJavaFile = { + null + }, + getSAFUri = { + treeUri + } + )) + fromUri(treeUri)?.let { createSnapshot(it,true) } } - )) + } catch (e:IllegalArgumentException) { + methods.value.showPopUpMessage("This Directory is already set as Download Directory") + } + GlobalScope.launch { + setDownloadDirectory(File(fileManager.fromUri(treeUri))) + createDirectories() + } } - @Suppress("DEPRECATION")// By Default Save Files to /Music/SpotiFlyer/ - private val defaultBaseDir = SpotiFlyerBaseDir({ getDirType() }, - getJavaFile = {java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString() + "/SpotiFlyer/")}, - getSAFUri = { null } - ) - // Image Cache Path // We Will Handling Image relating operations using java.io.File (reason: Faster) actual val imageCachePath: String get() = methods.value.platformActions.imageCacheDir.absolutePath + "/" @@ -145,25 +159,9 @@ actual class Dir actual constructor( fileManager.create(dirPath.documentFile!!) } } - - /*try { - val yourAppDir = File(dirPath) - - if (!yourAppDir.exists() && !yourAppDir.isDirectory) { // create empty directory - if (yourAppDir.mkdirs()) { logger.i { "$dirPath created" } } else { - logger.e { "Unable to create Dir: $dirPath!" } - } - } else { - logger.i { "$dirPath already exists" } - } - } catch (e: SecurityException) { - //TRY USING SAF - Log.d("Directory","USING SAF to create $dirPath") - val file = DocumentFile.fromTreeUri(context, Uri.parse(defaultDir())) - DocumentFile.fromFile() - }*/ } + @Suppress("unused") actual suspend fun clearCache(): Unit = withContext(dispatcherIO) { try { java.io.File(imageCachePath).deleteRecursively() @@ -178,20 +176,20 @@ actual class Dir actual constructor( trackDetails: TrackDetails, postProcess: (track: TrackDetails) -> Unit, ): Unit = withContext(dispatcherIO) { - val songFile = java.io.File(imageCachePath+"Tracks/"+ removeIllegalChars(trackDetails.title) + ".mp3") + val mediaFile = java.io.File(imageCachePath+"Tracks/"+ removeIllegalChars(trackDetails.title) + ".mp3") try { - /*Make intermediate Dirs if they don't exist yet*/ - if(!songFile.exists()) { - songFile.parentFile?.mkdirs() + if(!mediaFile.exists()) { + mediaFile.parentFile?.mkdirs() } + // Write Bytes to Media File + mediaFile.writeBytes(mp3ByteArray) - if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray) - - Mp3File(songFile) + // Add Metadata to Media File + Mp3File(mediaFile) .removeAllTags() .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails,songFile.absolutePath) + .setId3v2TagsAndSaveFile(trackDetails,mediaFile.absolutePath) // Copy File to Desired Location val documentFile = when(getDirType()){ @@ -201,105 +199,52 @@ actual class Dir actual constructor( BaseDirectory.ActiveBaseDirType.JavaFileBaseDir -> { fileManager.fromPath(trackDetails.outputFilePath) } - }.also { fileManager.create(it!!) } + }.also { + // Create Desired File if it doesn't exists yet + fileManager.create(it!!) + } try { fileManager.copyFileContents( - fileManager.fromRawFile(songFile), + fileManager.fromRawFile(mediaFile), documentFile!! ) - songFile.deleteOnExit() - /*val inStream = FileInputStream(songFile) - - val buffer = ByteArray(1024) - var readLen: Int - while (inStream.read(buffer).also { readLen = it } != -1) { - outStream?.write(buffer, 0, readLen) - } - inStream.close() - // write the output file (You have now copied the file) - outStream?.flush() - outStream?.close()*/ - + mediaFile.deleteOnExit() }catch (e:Exception) { e.printStackTrace() } documentFile?.let { - addToLibrary(File(it)) + addToLibrary(File(it),trackDetails) } - - /*when (trackDetails.outputFilePath.substringAfterLast('.')) { - ".mp3" -> { - Mp3File(songFile) - .removeAllTags() - .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails,songFile.absolutePath) - - // Copy File to DocumentUri - val documentFile = DocumentFile.fromSingleUri(context,Uri.parse(trackDetails.outputFilePath)) - try { - val outStream = context.contentResolver.openOutputStream(documentFile?.uri!!) - val inStream = FileInputStream(songFile) - - val buffer = ByteArray(1024) - var readLen: Int - while (inStream.read(buffer).also { readLen = it } != -1) { - outStream?.write(buffer, 0, readLen) - } - inStream.close() - // write the output file (You have now copied the file) - outStream?.flush() - outStream?.close() - - }catch (e:Exception) { - e.printStackTrace() - } - - documentFile?.let { - addToLibrary(File(it)) - } - } - ".m4a" -> { - *//*FFmpeg.executeAsync( - "-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}" - ){ _, returnCode -> - when (returnCode) { - Config.RETURN_CODE_SUCCESS -> { - //FFMPEG task Completed - logger.d{ "Async command execution completed successfully." } - scope.launch { - Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")) - .removeAllTags() - .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails) - addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3") - } - } - Config.RETURN_CODE_CANCEL -> { - logger.d{"Async command execution cancelled by user."} - } - else -> { - logger.d { "Async command execution failed with rc=$returnCode" } - } - } - }*//* - } - else -> { - // TODO - } - }*/ }catch (e:Exception){ - withContext(Dispatchers.Main){ - //Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() - } - if(songFile.exists()) songFile.delete() - logger.e { "${songFile.absolutePath} could not be created" } + e.printStackTrace() + if(mediaFile.exists()) mediaFile.delete() + logger.e { "${mediaFile.absolutePath} could not be created" } } } - actual fun addToLibrary(file: File) { -// methods.value.platformActions.addToLibrary(path) + actual fun addToLibrary(file: File,track: TrackDetails) { + try { + when (getDirType()) { + BaseDirectory.ActiveBaseDirType.SafBaseDir -> { + val values = ContentValues(4).apply { + put(MediaStore.Audio.Media.TITLE, track.title) + put(MediaStore.Audio.Media.DISPLAY_NAME, track.title) + put(MediaStore.Audio.Media.DATE_ADDED, + (System.currentTimeMillis() / 1000).toInt()) + put(MediaStore.Audio.Media.MIME_TYPE, "audio/mpeg") + } + context.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + values) + } + BaseDirectory.ActiveBaseDirType.JavaFileBaseDir -> { + file.documentFile?.getFullPath()?.let { + methods.value.platformActions.addToLibrary(it) + } + } + } + } catch (e:Exception) { e.printStackTrace() } } actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){ @@ -319,6 +264,7 @@ actual class Dir actual constructor( } } + @Suppress("BlockingMethodInNonBlockingContext") actual suspend fun cacheImage(image: Any, path: String):Unit = withContext(dispatcherIO) { try { java.io.File(path).parentFile?.mkdirs() @@ -330,6 +276,7 @@ actual class Dir actual constructor( } } + @Suppress("BlockingMethodInNonBlockingContext") private suspend fun freshImage(url: String): Bitmap? = withContext(dispatcherIO) { try { val source = URL(url) @@ -372,59 +319,17 @@ actual class Dir actual constructor( subFolder: String, extension: String, ):File { + // Create Intermediate Directories val file = fileManager.create( defaultDir().documentFile!!, //Base Dir DirectorySegment(removeIllegalChars(type)), DirectorySegment(removeIllegalChars(subFolder)), - + FileSegment(removeIllegalChars(itemName) + extension) ) - return File(file?.clone(FileSegment(removeIllegalChars(itemName) + extension)))/*.also { - if(fileManager.getLength(it.documentFile!!) == 0L){ - fileManager.delete(it.documentFile!!) - } - }*/ - /*GlobalScope.launch { - // Create Intermediate Directories - var file = defaultDir().documentFile - file = file.findFile(removeIllegalChars(type)) - ?: file.createDirectory(removeIllegalChars(type)) - ?: throw Exception("Couldn't Find/Create $type Directory") - - if (subFolder.isNotEmpty()) file.findFile(removeIllegalChars(subFolder)) - ?: file.createDirectory(removeIllegalChars(subFolder)) - ?: throw Exception("Couldn't Find/Create $subFolder Directory") - - } - val sep = "%2F" - val finalUri = defaultDir().documentFile.uri.toString() + sep + - removeIllegalChars(type) + sep + - removeIllegalChars(subFolder) + sep + - removeIllegalChars(itemName) + extension - return File( - DocumentFile.fromSingleUri(context,Uri.parse(finalUri))!! - ).also { - Log.d("Final Output File",it.documentFile.uri.toString()) - }*/ - - - /*file = file?.findFile(removeIllegalChars(type)) - ?: file?.createDirectory(removeIllegalChars(type)) - ?: throw Exception("Couldn't Find/Create $type Directory") - - if (subFolder.isNotEmpty()) file = file.findFile(removeIllegalChars(subFolder)) - ?: file.createDirectory(removeIllegalChars(subFolder)) - ?: throw Exception("Couldn't Find/Create $subFolder Directory") - - // TODO check Mime - file = file.findFile(removeIllegalChars(itemName)) - ?: file.createFile("audio/mpeg",removeIllegalChars(itemName)) - ?: throw Exception("Couldn't Find/Create ${removeIllegalChars(itemName) + extension} File") - Log.d("Final Output File",file.uri.toString()) - return File(file).also { - val size = it.documentFile.length() - Log.d("File size", size.toString()) - if(size == 0L) it.documentFile.delete() - }*/ + if(fileManager.getLength(it.documentFile!!) == 0L) fileManager.delete(it.documentFile!!) + } + + //?.clone(FileSegment(removeIllegalChars(itemName) + extension))) } } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt index 58414e5a..78ff1919 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt @@ -18,39 +18,31 @@ package com.shabinder.common.di.worker import android.annotation.SuppressLint import android.app.DownloadManager -import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.Service -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.IntentFilter -import android.net.Uri import android.os.Build import android.os.IBinder import android.os.PowerManager import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat -import androidx.core.net.toUri import co.touchlab.kermit.Kermit -import com.github.k1rakishou.fsaf.FileManager -import com.github.k1rakishou.fsaf.file.AbstractFile import com.shabinder.common.di.* import com.shabinder.common.di.providers.getData import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus +import com.shabinder.common.models.Status import com.shabinder.common.models.TrackDetails import com.shabinder.downloader.models.formats.Format -import com.shabinder.common.models.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.collect @@ -128,8 +120,7 @@ class ForegroundService : Service(), CoroutineScope { val downloadObjects: ArrayList? = ( it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList( - "object" - ) + "object") ) downloadObjects?.let { list -> @@ -216,13 +207,11 @@ class ForegroundService : Service(), CoroutineScope { is DownloadResult.Error -> { launch { logger.d(tag) { it.message } - /*logger.d(tag) { "${track.title} Requesting Download thru Android DM" } - downloadUsingDM(url, track.outputFilePath, track)*/ removeFromNotification("Downloading ${track.title}") failed++ + updateNotification() + sendTrackBroadcast(Status.FAILED.name,track) } - updateNotification() - sendTrackBroadcast(Status.FAILED.name,track) } is DownloadResult.Progress -> { @@ -252,14 +241,10 @@ class ForegroundService : Service(), CoroutineScope { } logger.d(tag) { "${track.title} Download Completed" } downloaded++ - } catch ( - e: Exception - ) { - // Try downloading using android DM + } catch (e: Exception) { + // Download Failed logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" } failed++ - /*logger.d(tag) { "${track.title} Requesting Download thru Android DM" } - downloadUsingDM(url, track.outputFilePath, track)*/ } removeFromNotification("Downloading ${track.title}") } @@ -267,54 +252,6 @@ class ForegroundService : Service(), CoroutineScope { } } - /** - * If Custom Downloader Fails , Android Download Manager To RESCUE!! - **/ - private fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails) { - launch { - val uri = Uri.parse(url) - val request = DownloadManager.Request(uri).apply { - setAllowedNetworkTypes( - DownloadManager.Request.NETWORK_WIFI or - DownloadManager.Request.NETWORK_MOBILE - ) - setAllowedOverRoaming(false) - setTitle(track.title) - setDescription("Spotify Downloader Working Up here...") - setDestinationUri(File(outputDir).toUri()) - setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - } - - // Start Download - val downloadID = downloadManager.enqueue(request) - logger.d("DownloadManager") { "Download Request Sent" } - - val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - // Fetching the download id received with the broadcast - val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) - // Checking if the received broadcast is for our enqueued download by matching download id - if (downloadID == id) { - allTracksStatus[track.title] = DownloadStatus.Converting - launch { dir.saveFileWithMetadata(byteArrayOf(), track){}; converted++ } - // Unregister this broadcast Receiver - this@ForegroundService.unregisterReceiver(this) - } - } - } - registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) - } - } - - /** - * This is the method that can be called to update the Notification - */ - private fun updateNotification() { - val mNotificationManager: NotificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - mNotificationManager.notify(notificationId, getNotification()) - } - private fun releaseWakeLock() { logger.d(tag) { "Releasing Wake Lock" } try { @@ -341,13 +278,17 @@ class ForegroundService : Service(), CoroutineScope { service.createNotificationChannel(channel) } + /* + * Time To Wrap UP + * - `Clean Up` and `Stop this Foreground Service` + * */ private fun killService() { launch { logger.d(tag) { "Killing Self" } messageList = mutableListOf("Cleaning And Exiting", "", "", "", "") downloadService.close() updateNotification() - // dir.defaultDir().documentFile?.let { cleanFiles(it,dir.fileManager,logger) } + dir.defaultDir().documentFile?.let { cleanFiles(it,dir.fileManager,logger) } cleanFiles(File(dir.imageCachePath + "Tracks/"),logger) messageList = mutableListOf("", "", "", "", "") releaseWakeLock() @@ -375,6 +316,9 @@ class ForegroundService : Service(), CoroutineScope { } } + /* + * Create A New Notification with all the updated data + * */ private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run { setSmallIcon(R.drawable.ic_download_arrow) setContentTitle("Total: $total Completed:$converted Failed:$failed") @@ -402,6 +346,15 @@ class ForegroundService : Service(), CoroutineScope { updateNotification() } + /** + * This is the method that can be called to update the Notification + */ + private fun updateNotification() { + val mNotificationManager: NotificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + mNotificationManager.notify(notificationId, getNotification()) + } + private fun sendTrackBroadcast(action: String, track: TrackDetails) { val intent = Intent().apply { setAction(action) diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/Utils.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/Utils.kt index 43b6830d..e20f1de1 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/Utils.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/Utils.kt @@ -37,7 +37,10 @@ fun cleanFiles(directory: AbstractFile,fm: FileManager,logger: Kermit) { if (fm.isDirectory(file)) { cleanFiles(file, fm, logger) } else if (fm.isFile(file)) { - if (file.getFullPath().substringAfterLast(".") != "mp3") { + if (file.getFullPath().substringAfterLast(".") != "mp3" + || + fm.getLength(file) == 0L + ) { logger.d("Files Cleaning") { "Cleaning ${file.getFullPath()}" } fm.delete(file) } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt index ef7475c9..35840522 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt @@ -56,7 +56,7 @@ expect class Dir ( suspend fun loadImage(url: String): Picture suspend fun clearCache() suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails,postProcess:(track: TrackDetails)->Unit = {}) - fun addToLibrary(file: File) + fun addToLibrary(file: File,track: TrackDetails) fun finalOutputFile(itemName: String, type: String, subFolder: String, extension: String = ".mp3"): File fun finalOutputPath(itemName: String, type: String, subFolder: String, extension: String = ".mp3"): String }