mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Merge remote-tracking branch 'origin/main' into main
# Conflicts: # android/build.gradle.kts # android/src/main/java/com/shabinder/spotiflyer/App.kt # android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt # common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt # common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt # common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt
This commit is contained in:
commit
6d6a73c713
@ -1,62 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- <color name="colorPrimary">#2d6c55</color>
|
|
||||||
<color name="colorPrimaryDark">#235644</color>
|
|
||||||
<color name="colorAccent">#ff9c40</color>
|
|
||||||
|
|
||||||
<color name="colorCyanListClick">#d6c8f5f3</color>
|
|
||||||
<color name="colorListDivider">#89606060</color>
|
|
||||||
|
|
||||||
<color name="pathLayoutBgColor">#70ffffff</color>
|
|
||||||
<color name="chevronBgColor">#50ffffff</color>
|
|
||||||
<color name="inactiveGradientColor">#53000000</color>
|
|
||||||
|
|
||||||
<!– dialog colors –>
|
|
||||||
<color name="memory_status_color">#de6565</color>
|
|
||||||
<color name="memory_bar_color">#7bde65</color>
|
|
||||||
<color name="new_folder_color">#c14b84</color>
|
|
||||||
<color name="select_color">#6b3fa1</color>
|
|
||||||
<color name="cancel_color">#3fa19f</color>-->
|
|
||||||
|
|
||||||
<array name="default_light">
|
|
||||||
<!-- Overview -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- Top Header bg -->
|
|
||||||
<item>@android:color/white</item> <!-- header text -->
|
|
||||||
<item>@android:color/white</item> <!-- list bg -->
|
|
||||||
<item>@android:color/black</item> <!-- storage list name text -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- free space text -->
|
|
||||||
<item>@color/colorAccent</item> <!-- memory bar -->
|
|
||||||
|
|
||||||
<!-- secondary dialog colors -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- address bar bg -->
|
|
||||||
<item>@android:color/white</item> <!-- list bg -->
|
|
||||||
<item>@android:color/black</item> <!-- list text -->
|
|
||||||
<item>@android:color/white</item> <!-- address bar tint -->
|
|
||||||
<item>@color/chevronBgColor</item> <!-- new folder hint tint -->
|
|
||||||
<item>#da6c6c</item> <!-- select button color -->
|
|
||||||
<item>#da6c6c</item> <!-- new folder layour bg -->
|
|
||||||
<item>#da6c6c</item> <!-- new folder layour bg -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- new folder layour bg -->
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<array name="default_dark">
|
|
||||||
<!-- Overview -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- Top Header bg -->
|
|
||||||
<item>@android:color/white</item> <!-- header text -->
|
|
||||||
<item>@android:color/black</item> <!-- list bg -->
|
|
||||||
<item>@android:color/white</item> <!-- storage list name text -->
|
|
||||||
<item>#da6c6c</item> <!-- free space text -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- memory bar -->
|
|
||||||
|
|
||||||
<!-- secondary dialog colors -->
|
|
||||||
<item>@color/colorPrimary</item> <!-- address bar bg -->
|
|
||||||
<item>@android:color/black</item> <!-- list bg -->
|
|
||||||
<item>@android:color/white</item> <!-- list text -->
|
|
||||||
<item>@android:color/white</item> <!-- address bar tint -->
|
|
||||||
<item>@color/grey</item> <!-- new folder hint tint -->
|
|
||||||
<item>#da6c6c</item> <!-- select button color -->
|
|
||||||
<item>#da6c6c</item> <!-- new folder layour bg -->
|
|
||||||
<item>#da6c6c</item> <!-- new multi fab -->
|
|
||||||
<item>#da6c6c</item> <!-- new multi fab -->
|
|
||||||
</array>
|
|
||||||
</resources>
|
|
@ -45,5 +45,11 @@ kotlin {
|
|||||||
implementation("co.touchlab:stately-iso-collections:$statelyIsoVersion")
|
implementation("co.touchlab:stately-iso-collections:$statelyIsoVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api("com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1")
|
||||||
|
api("androidx.documentfile:documentfile:1.0.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
import com.github.k1rakishou.fsaf.file.AbstractFile
|
||||||
|
|
||||||
|
// Use Storage Access Framework `SAF`
|
||||||
|
actual data class File(
|
||||||
|
val documentFile: AbstractFile?
|
||||||
|
)
|
@ -8,7 +8,7 @@ actual interface PlatformActions {
|
|||||||
const val SharedPreferencesKey = "configurations"
|
const val SharedPreferencesKey = "configurations"
|
||||||
}
|
}
|
||||||
|
|
||||||
val imageCacheDir: String
|
val imageCacheDir: java.io.File
|
||||||
|
|
||||||
val sharedPreferences: SharedPreferences?
|
val sharedPreferences: SharedPreferences?
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ actual interface PlatformActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actual val StubPlatformActions = object: PlatformActions {
|
actual val StubPlatformActions = object: PlatformActions {
|
||||||
override val imageCacheDir: String = ""
|
override val imageCacheDir = java.io.File("/")
|
||||||
|
|
||||||
override val sharedPreferences: SharedPreferences? = null
|
override val sharedPreferences: SharedPreferences? = null
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.k1rakishou.fsaf.manager.base_directory.BaseDirectory
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class SpotiFlyerBaseDir(
|
||||||
|
private val getDirType: ()-> ActiveBaseDirType,
|
||||||
|
private val getJavaFile: ()-> File?,
|
||||||
|
private val getSAFUri: ()-> Uri?
|
||||||
|
): BaseDirectory() {
|
||||||
|
|
||||||
|
override fun currentActiveBaseDirType(): ActiveBaseDirType = getDirType()
|
||||||
|
|
||||||
|
override fun getDirFile(): File? = getJavaFile()
|
||||||
|
|
||||||
|
override fun getDirUri(): Uri? = getSAFUri()
|
||||||
|
}
|
@ -32,12 +32,12 @@ data class TrackDetails(
|
|||||||
var comment: String? = null,
|
var comment: String? = null,
|
||||||
var lyrics: String? = null,
|
var lyrics: String? = null,
|
||||||
var trackUrl: String? = null,
|
var trackUrl: String? = null,
|
||||||
var albumArtPath: String,
|
var albumArtPath: String, // UriString in Android
|
||||||
var albumArtURL: String,
|
var albumArtURL: String,
|
||||||
var source: Source,
|
var source: Source,
|
||||||
val progress: Int = 2,
|
val progress: Int = 2,
|
||||||
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||||
var outputFilePath: String,
|
var outputFilePath: String, // UriString in Android
|
||||||
var videoID: String? = null,
|
var videoID: String? = null,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
expect class File
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
actual typealias File = java.io.File
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
actual data class File(
|
||||||
|
val path: String
|
||||||
|
)
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
actual data class File(
|
||||||
|
val path: String
|
||||||
|
)
|
@ -41,7 +41,6 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(compose.materialIconsExtended)
|
||||||
implementation(Extras.mp3agic)
|
implementation(Extras.mp3agic)
|
||||||
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
|
||||||
// implementation(files("$rootDir/libs/mobile-ffmpeg.aar"))
|
// implementation(files("$rootDir/libs/mobile-ffmpeg.aar"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,8 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
|
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails,tempMp3Path:String) {
|
||||||
val id3v2Tag = ID3v24Tag().apply {
|
val id3v2Tag = ID3v24Tag().apply {
|
||||||
|
|
||||||
artist = track.artists.joinToString(", ")
|
artist = track.artists.joinToString(", ")
|
||||||
title = track.title
|
title = track.title
|
||||||
album = track.albumName
|
album = track.albumName
|
||||||
@ -118,9 +117,9 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
|
|||||||
fis.close()
|
fis.close()
|
||||||
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
|
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
|
||||||
this.id3v2Tag = id3v2Tag
|
this.id3v2Tag = id3v2Tag
|
||||||
saveFile(track.outputFilePath)
|
saveFile(tempMp3Path)
|
||||||
} catch (e: java.io.FileNotFoundException) {
|
} catch (e: java.io.FileNotFoundException) {
|
||||||
Log.e("Error", "Couldn't Write Cached Mp3 Album Art, error: ${e.stackTrace}")
|
Log.e("Error", "Couldn't Write Cached Mp3 Album Art, Downloading And Trying Again, error: ${e.message}")
|
||||||
try {
|
try {
|
||||||
// Image Still Not Downloaded!
|
// Image Still Not Downloaded!
|
||||||
// Lets Download Now and Write it into Album Art
|
// Lets Download Now and Write it into Album Art
|
||||||
@ -130,7 +129,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
|
|||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg")
|
id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg")
|
||||||
this.id3v2Tag = id3v2Tag
|
this.id3v2Tag = id3v2Tag
|
||||||
saveFile(track.outputFilePath)
|
saveFile(tempMp3Path)
|
||||||
}
|
}
|
||||||
is DownloadResult.Progress -> {} // Nothing for Now , no progress bar to show
|
is DownloadResult.Progress -> {} // Nothing for Now , no progress bar to show
|
||||||
}
|
}
|
||||||
@ -143,6 +142,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Mp3File.saveFile(filePath: String) {
|
fun Mp3File.saveFile(filePath: String) {
|
||||||
|
Log.d("Mp3 File Save",filePath)
|
||||||
save(filePath.substringBeforeLast('.') + ".new.mp3")
|
save(filePath.substringBeforeLast('.') + ".new.mp3")
|
||||||
val m4aFile = File(filePath)
|
val m4aFile = File(filePath)
|
||||||
m4aFile.delete()
|
m4aFile.delete()
|
||||||
|
@ -18,33 +18,29 @@ package com.shabinder.common.di.worker
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.net.toUri
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.*
|
import com.shabinder.common.di.*
|
||||||
|
import com.shabinder.common.di.providers.getData
|
||||||
import com.shabinder.common.di.utils.ParallelExecutor
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
|
import com.shabinder.common.models.Status
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.downloader.models.formats.Format
|
import com.shabinder.downloader.models.formats.Format
|
||||||
import com.shabinder.common.models.Status
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -124,8 +120,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
|
|
||||||
val downloadObjects: ArrayList<TrackDetails>? = (
|
val downloadObjects: ArrayList<TrackDetails>? = (
|
||||||
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
||||||
"object"
|
"object")
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
downloadObjects?.let { list ->
|
downloadObjects?.let { list ->
|
||||||
@ -212,13 +207,11 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
launch {
|
launch {
|
||||||
logger.d(tag) { it.message }
|
logger.d(tag) { it.message }
|
||||||
/*logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
|
|
||||||
downloadUsingDM(url, track.outputFilePath, track)*/
|
|
||||||
removeFromNotification("Downloading ${track.title}")
|
removeFromNotification("Downloading ${track.title}")
|
||||||
failed++
|
failed++
|
||||||
|
updateNotification()
|
||||||
|
sendTrackBroadcast(Status.FAILED.name,track)
|
||||||
}
|
}
|
||||||
updateNotification()
|
|
||||||
sendTrackBroadcast(Status.FAILED.name,track)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadResult.Progress -> {
|
is DownloadResult.Progress -> {
|
||||||
@ -248,14 +241,10 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
logger.d(tag) { "${track.title} Download Completed" }
|
logger.d(tag) { "${track.title} Download Completed" }
|
||||||
downloaded++
|
downloaded++
|
||||||
} catch (
|
} catch (e: Exception) {
|
||||||
e: Exception
|
// Download Failed
|
||||||
) {
|
|
||||||
// Try downloading using android DM
|
|
||||||
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
||||||
failed++
|
failed++
|
||||||
/*logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
|
|
||||||
downloadUsingDM(url, track.outputFilePath, track)*/
|
|
||||||
}
|
}
|
||||||
removeFromNotification("Downloading ${track.title}")
|
removeFromNotification("Downloading ${track.title}")
|
||||||
}
|
}
|
||||||
@ -263,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() {
|
private fun releaseWakeLock() {
|
||||||
logger.d(tag) { "Releasing Wake Lock" }
|
logger.d(tag) { "Releasing Wake Lock" }
|
||||||
try {
|
try {
|
||||||
@ -337,34 +278,18 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
service.createNotificationChannel(channel)
|
service.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Cleaning All Residual Files except Mp3 Files
|
* Time To Wrap UP
|
||||||
**/
|
* - `Clean Up` and `Stop this Foreground Service`
|
||||||
private fun cleanFiles(dir: File) {
|
* */
|
||||||
logger.d(tag) { "Starting Cleaning in ${dir.path} " }
|
|
||||||
val fList = dir.listFiles()
|
|
||||||
fList?.let {
|
|
||||||
for (file in fList) {
|
|
||||||
if (file.isDirectory) {
|
|
||||||
cleanFiles(file)
|
|
||||||
} else if (file.isFile) {
|
|
||||||
if (file.path.toString().substringAfterLast(".") != "mp3") {
|
|
||||||
logger.d(tag) { "Cleaning ${file.path}" }
|
|
||||||
file.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun killService() {
|
private fun killService() {
|
||||||
launch {
|
launch {
|
||||||
logger.d(tag) { "Killing Self" }
|
logger.d(tag) { "Killing Self" }
|
||||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||||
downloadService.close()
|
downloadService.close()
|
||||||
updateNotification()
|
updateNotification()
|
||||||
cleanFiles(File(dir.defaultDir()))
|
dir.defaultDir().documentFile?.let { cleanFiles(it,dir.fileManager,logger) }
|
||||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
cleanFiles(File(dir.imageCachePath + "Tracks/"),logger)
|
||||||
messageList = mutableListOf("", "", "", "", "")
|
messageList = mutableListOf("", "", "", "", "")
|
||||||
releaseWakeLock()
|
releaseWakeLock()
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
@ -391,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 {
|
private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run {
|
||||||
setSmallIcon(R.drawable.ic_download_arrow)
|
setSmallIcon(R.drawable.ic_download_arrow)
|
||||||
setContentTitle("Total: $total Completed:$converted Failed:$failed")
|
setContentTitle("Total: $total Completed:$converted Failed:$failed")
|
||||||
@ -418,6 +346,15 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
updateNotification()
|
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) {
|
private fun sendTrackBroadcast(action: String, track: TrackDetails) {
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
setAction(action)
|
setAction(action)
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.shabinder.common.di.worker
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Kermit
|
||||||
|
import com.github.k1rakishou.fsaf.FileManager
|
||||||
|
import com.github.k1rakishou.fsaf.file.AbstractFile
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleaning All Residual Files except Mp3 Files
|
||||||
|
**/
|
||||||
|
fun cleanFiles(dir: File,logger: Kermit) {
|
||||||
|
try {
|
||||||
|
logger.d("File Cleaning") { "Starting Cleaning in ${dir.path} " }
|
||||||
|
val fList = dir.listFiles()
|
||||||
|
fList?.let {
|
||||||
|
for (file in fList) {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
cleanFiles(file, logger)
|
||||||
|
} else if (file.isFile) {
|
||||||
|
if (file.path.toString().substringAfterLast(".") != "mp3") {
|
||||||
|
logger.d("Files Cleaning") { "Cleaning ${file.path}" }
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e:Exception) { e.printStackTrace() }
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Cleaning All Residual Files except Mp3 Files
|
||||||
|
**/
|
||||||
|
fun cleanFiles(directory: AbstractFile,fm: FileManager,logger: Kermit) {
|
||||||
|
try {
|
||||||
|
logger.d("Files Cleaning") { "Starting Cleaning in ${directory.getFullPath()} " }
|
||||||
|
val fList = fm.listFiles(directory)
|
||||||
|
for (file in fList) {
|
||||||
|
if (fm.isDirectory(file)) {
|
||||||
|
cleanFiles(file, fm, logger)
|
||||||
|
} else if (fm.isFile(file)) {
|
||||||
|
if (file.getFullPath().substringAfterLast(".") != "mp3"
|
||||||
|
||
|
||||||
|
fm.getLength(file) == 0L
|
||||||
|
) {
|
||||||
|
logger.d("Files Cleaning") { "Cleaning ${file.getFullPath()}" }
|
||||||
|
fm.delete(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e:Exception) { e.printStackTrace() }
|
||||||
|
}
|
@ -21,13 +21,10 @@ import com.shabinder.common.di.providers.GaanaProvider
|
|||||||
import com.shabinder.common.di.providers.SpotifyProvider
|
import com.shabinder.common.di.providers.SpotifyProvider
|
||||||
import com.shabinder.common.di.providers.YoutubeMp3
|
import com.shabinder.common.di.providers.YoutubeMp3
|
||||||
import com.shabinder.common.di.providers.YoutubeMusic
|
import com.shabinder.common.di.providers.YoutubeMusic
|
||||||
|
import com.shabinder.common.di.providers.YoutubeProvider
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class FetchPlatformQueryResult(
|
class FetchPlatformQueryResult(
|
||||||
val gaanaProvider: GaanaProvider,
|
val gaanaProvider: GaanaProvider,
|
||||||
|
@ -18,8 +18,8 @@ package com.shabinder.common.di.providers
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.Dir
|
import com.shabinder.common.di.Dir
|
||||||
import com.shabinder.common.di.finalOutputDir
|
|
||||||
import com.shabinder.common.di.gaana.GaanaRequests
|
import com.shabinder.common.di.gaana.GaanaRequests
|
||||||
|
import com.shabinder.common.di.getNameURL
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
@ -136,7 +136,7 @@ class GaanaProvider(
|
|||||||
title = it.track_title,
|
title = it.track_title,
|
||||||
artists = it.artist.map { artist -> artist?.name.toString() },
|
artists = it.artist.map { artist -> artist?.name.toString() },
|
||||||
durationSec = it.duration,
|
durationSec = it.duration,
|
||||||
albumArtPath = dir.imageCacheDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
|
albumArtPath = dir.imageCachePath + getNameURL(it.artworkLink),
|
||||||
albumName = it.album_title,
|
albumName = it.album_title,
|
||||||
year = it.release_date,
|
year = it.release_date,
|
||||||
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
|
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
|
||||||
@ -144,16 +144,15 @@ class GaanaProvider(
|
|||||||
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
||||||
source = Source.Gaana,
|
source = Source.Gaana,
|
||||||
albumArtURL = it.artworkLink.replace("http:","https:"),
|
albumArtURL = it.artworkLink.replace("http:","https:"),
|
||||||
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
outputFilePath = dir.finalOutputPath(it.track_title, type, subFolder /*,".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private fun GaanaTrack.updateStatusIfPresent(folderType: String, subFolder: String) {
|
private fun GaanaTrack.updateStatusIfPresent(folderType: String, subFolder: String) {
|
||||||
if (dir.isPresent(
|
if (dir.isPresent(
|
||||||
dir.finalOutputDir(
|
dir.finalOutputFile(
|
||||||
track_title,
|
track_title,
|
||||||
folderType,
|
folderType,
|
||||||
subFolder,
|
subFolder,
|
||||||
dir.defaultDir()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) { // Download Already Present!!
|
) { // Download Already Present!!
|
||||||
|
@ -17,14 +17,10 @@
|
|||||||
package com.shabinder.common.di.providers
|
package com.shabinder.common.di.providers
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import co.touchlab.stately.ensureNeverFrozen
|
|
||||||
import co.touchlab.stately.freeze
|
|
||||||
import com.shabinder.common.di.Dir
|
import com.shabinder.common.di.Dir
|
||||||
import com.shabinder.common.di.TokenStore
|
import com.shabinder.common.di.TokenStore
|
||||||
import com.shabinder.common.di.createHttpClient
|
import com.shabinder.common.di.createHttpClient
|
||||||
import com.shabinder.common.di.finalOutputDir
|
import com.shabinder.common.di.getNameURL
|
||||||
import com.shabinder.common.di.kotlinxSerializer
|
|
||||||
import com.shabinder.common.di.ktorHttpClient
|
|
||||||
import com.shabinder.common.di.spotify.SpotifyRequests
|
import com.shabinder.common.di.spotify.SpotifyRequests
|
||||||
import com.shabinder.common.di.spotify.authenticateSpotify
|
import com.shabinder.common.di.spotify.authenticateSpotify
|
||||||
import com.shabinder.common.models.NativeAtomicReference
|
import com.shabinder.common.models.NativeAtomicReference
|
||||||
@ -35,12 +31,8 @@ import com.shabinder.common.models.spotify.Image
|
|||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
import com.shabinder.common.models.spotify.Track
|
import com.shabinder.common.models.spotify.Track
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.auth.*
|
|
||||||
import io.ktor.client.features.auth.providers.*
|
|
||||||
import io.ktor.client.features.defaultRequest
|
import io.ktor.client.features.defaultRequest
|
||||||
import io.ktor.client.features.json.JsonFeature
|
|
||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
import kotlin.native.concurrent.SharedImmutable
|
|
||||||
|
|
||||||
class SpotifyProvider(
|
class SpotifyProvider(
|
||||||
private val tokenStore: TokenStore,
|
private val tokenStore: TokenStore,
|
||||||
@ -224,28 +216,28 @@ class SpotifyProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Track>.toTrackDetailsList(type: String, subFolder: String) = this.map {
|
private fun List<Track>.toTrackDetailsList(type: String, subFolder: String) = this.map {
|
||||||
|
val albumArtLink = it.album?.images?.firstOrNull()?.url.toString()
|
||||||
TrackDetails(
|
TrackDetails(
|
||||||
title = it.name.toString(),
|
title = it.name.toString(),
|
||||||
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
||||||
durationSec = (it.duration_ms / 1000).toInt(),
|
durationSec = (it.duration_ms / 1000).toInt(),
|
||||||
albumArtPath = dir.imageCacheDir() + (it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg",
|
albumArtPath = dir.imageCachePath + getNameURL(albumArtLink),
|
||||||
albumName = it.album?.name,
|
albumName = it.album?.name,
|
||||||
year = it.album?.release_date,
|
year = it.album?.release_date,
|
||||||
comment = "Genres:${it.album?.genres?.joinToString()}",
|
comment = "Genres:${it.album?.genres?.joinToString()}",
|
||||||
trackUrl = it.href,
|
trackUrl = it.href,
|
||||||
downloaded = it.downloaded,
|
downloaded = it.downloaded,
|
||||||
source = Source.Spotify,
|
source = Source.Spotify,
|
||||||
albumArtURL = it.album?.images?.firstOrNull()?.url.toString(),
|
albumArtURL = albumArtLink,
|
||||||
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
outputFilePath = dir.finalOutputPath(it.name.toString(), type, subFolder/*,".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private fun Track.updateStatusIfPresent(folderType: String, subFolder: String) {
|
private fun Track.updateStatusIfPresent(folderType: String, subFolder: String) {
|
||||||
if (dir.isPresent(
|
if (dir.isPresent(
|
||||||
dir.finalOutputDir(
|
dir.finalOutputFile(
|
||||||
name.toString(),
|
name.toString(),
|
||||||
folderType,
|
folderType,
|
||||||
subFolder,
|
subFolder,
|
||||||
dir.defaultDir()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) { // Download Already Present!!
|
) { // Download Already Present!!
|
||||||
|
@ -14,9 +14,11 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.common.di
|
package com.shabinder.common.di.providers
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
|
import com.shabinder.common.di.Dir
|
||||||
|
import com.shabinder.common.di.getNameURL
|
||||||
import com.shabinder.common.di.utils.removeIllegalChars
|
import com.shabinder.common.di.utils.removeIllegalChars
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
@ -106,15 +108,14 @@ class YoutubeProvider(
|
|||||||
title = it.title ?: "N/A",
|
title = it.title ?: "N/A",
|
||||||
artists = listOf(it.author ?: "N/A"),
|
artists = listOf(it.author ?: "N/A"),
|
||||||
durationSec = it.lengthSeconds,
|
durationSec = it.lengthSeconds,
|
||||||
albumArtPath = dir.imageCacheDir() + it.videoId + ".jpeg",
|
albumArtPath = dir.imageCachePath + getNameURL(coverUrl),
|
||||||
source = Source.YouTube,
|
source = Source.YouTube,
|
||||||
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
|
albumArtURL = coverUrl,
|
||||||
downloaded = if (dir.isPresent(
|
downloaded = if (dir.isPresent(
|
||||||
dir.finalOutputDir(
|
dir.finalOutputFile(
|
||||||
itemName = it.title ?: "N/A",
|
itemName = it.title ?: "N/A",
|
||||||
type = folderType,
|
type = folderType,
|
||||||
subFolder = subFolder,
|
subFolder = subFolder,
|
||||||
dir.defaultDir()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -122,7 +123,7 @@ class YoutubeProvider(
|
|||||||
else {
|
else {
|
||||||
DownloadStatus.NotDownloaded
|
DownloadStatus.NotDownloaded
|
||||||
},
|
},
|
||||||
outputFilePath = dir.finalOutputDir(it.title ?: "N/A", folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
|
outputFilePath = dir.finalOutputPath(it.title ?: "N/A", folderType, subFolder/*,".m4a"*/),
|
||||||
videoID = it.videoId
|
videoID = it.videoId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -160,15 +161,14 @@ class YoutubeProvider(
|
|||||||
title = name,
|
title = name,
|
||||||
artists = listOf(detail.author ?: "N/A"),
|
artists = listOf(detail.author ?: "N/A"),
|
||||||
durationSec = detail.lengthSeconds,
|
durationSec = detail.lengthSeconds,
|
||||||
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
|
albumArtPath = dir.imageCachePath + getNameURL(coverUrl),
|
||||||
source = Source.YouTube,
|
source = Source.YouTube,
|
||||||
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
albumArtURL = coverUrl,
|
||||||
downloaded = if (dir.isPresent(
|
downloaded = if (dir.isPresent(
|
||||||
dir.finalOutputDir(
|
dir.finalOutputFile(
|
||||||
itemName = name,
|
itemName = name,
|
||||||
type = folderType,
|
type = folderType,
|
||||||
subFolder = subFolder,
|
subFolder = subFolder,
|
||||||
defaultDir = dir.defaultDir()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -176,7 +176,7 @@ class YoutubeProvider(
|
|||||||
else {
|
else {
|
||||||
DownloadStatus.NotDownloaded
|
DownloadStatus.NotDownloaded
|
||||||
},
|
},
|
||||||
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
|
outputFilePath = dir.finalOutputPath(name, folderType, subFolder /*,".m4a"*/),
|
||||||
videoID = searchId
|
videoID = searchId
|
||||||
)
|
)
|
||||||
)
|
)
|
@ -90,6 +90,7 @@ fun removeIllegalChars(fileName: String): String {
|
|||||||
name = fileName.replace(c, '_')
|
name = fileName.replace(c, '_')
|
||||||
}
|
}
|
||||||
name = name.replace("\\s".toRegex(), "_")
|
name = name.replace("\\s".toRegex(), "_")
|
||||||
|
name = name.replace("/".toRegex(), "_")
|
||||||
name = name.replace("\\)".toRegex(), "")
|
name = name.replace("\\)".toRegex(), "")
|
||||||
name = name.replace("\\(".toRegex(), "")
|
name = name.replace("\\(".toRegex(), "")
|
||||||
name = name.replace("\\[".toRegex(), "")
|
name = name.replace("\\[".toRegex(), "")
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
import com.shabinder.common.di.providers.YoutubeMp3
|
import com.shabinder.common.di.providers.YoutubeMp3
|
||||||
|
import com.shabinder.common.di.providers.getData
|
||||||
import com.shabinder.common.di.utils.ParallelExecutor
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.models.AllPlatforms
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
|
import com.shabinder.common.di.providers.getData
|
||||||
import com.shabinder.common.di.utils.ParallelExecutor
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.models.AllPlatforms
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
|
@ -25,7 +25,6 @@ import com.shabinder.common.main.integration.SpotiFlyerMainImpl
|
|||||||
import com.shabinder.common.models.Consumer
|
import com.shabinder.common.models.Consumer
|
||||||
import com.shabinder.common.models.DownloadRecord
|
import com.shabinder.common.models.DownloadRecord
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface SpotiFlyerMain {
|
interface SpotiFlyerMain {
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ package com.shabinder.common.main.integration
|
|||||||
|
|
||||||
import co.touchlab.stately.ensureNeverFrozen
|
import co.touchlab.stately.ensureNeverFrozen
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
import com.arkivanov.decompose.lifecycle.doOnDestroy
|
|
||||||
import com.arkivanov.decompose.value.Value
|
import com.arkivanov.decompose.value.Value
|
||||||
import com.shabinder.common.di.Picture
|
import com.shabinder.common.di.Picture
|
||||||
import com.shabinder.common.di.utils.asValue
|
import com.shabinder.common.di.utils.asValue
|
||||||
|
Loading…
Reference in New Issue
Block a user