mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
(WIP)Android SAF
This commit is contained in:
parent
96fdd52ef4
commit
ea48d929a4
@ -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()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>(SpotiFlyerBaseDir({ getDirType() },
|
||||
getJavaFile = {
|
||||
java.io.File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString()
|
||||
+ "/SpotiFlyer/"
|
||||
)
|
||||
},
|
||||
getSAFUri = { null }
|
||||
))
|
||||
fileManager.apply {
|
||||
registerBaseDir<SpotiFlyerBaseDir>(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>(SpotiFlyerBaseDir(
|
||||
{ getDirType() },
|
||||
getJavaFile = {
|
||||
null
|
||||
},
|
||||
getSAFUri = {
|
||||
treeUri
|
||||
try {
|
||||
fileManager.apply {
|
||||
registerBaseDir<SpotiFlyerBaseDir>(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)))
|
||||
}
|
||||
}
|
||||
|
@ -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<TrackDetails>? = (
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user