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:
shabinder 2021-05-14 03:44:15 +05:30
commit 6d6a73c713
23 changed files with 157 additions and 196 deletions

View File

@ -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>
&lt;!&ndash; dialog colors &ndash;&gt;
<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>

View File

@ -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")
}
}
} }
} }

View File

@ -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?
)

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
expect class File

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
actual typealias File = java.io.File

View File

@ -0,0 +1,5 @@
package com.shabinder.common.models
actual data class File(
val path: String
)

View File

@ -0,0 +1,5 @@
package com.shabinder.common.models
actual data class File(
val path: String
)

View File

@ -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"))
} }
} }

View File

@ -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()

View File

@ -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,14 +207,12 @@ 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() updateNotification()
sendTrackBroadcast(Status.FAILED.name,track) sendTrackBroadcast(Status.FAILED.name,track)
} }
}
is DownloadResult.Progress -> { is DownloadResult.Progress -> {
allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress) allTracksStatus[track.title] = DownloadStatus.Downloading(it.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)

View File

@ -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() }
}

View File

@ -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,

View File

@ -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!!

View File

@ -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!!

View File

@ -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
) )
) )

View File

@ -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(), "")

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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