From 148d56956b3b3754dd961f775f8d53fe9c8ebf11 Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 28 Apr 2021 14:54:56 +0530 Subject: [PATCH 1/6] Dep Updates and Logging --- android/src/main/java/com/shabinder/spotiflyer/App.kt | 5 +++-- .../src/main/java/com/shabinder/spotiflyer/di/AppModule.kt | 7 ++++--- common/dependency-injection/build.gradle.kts | 4 ++-- .../kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt | 3 ++- .../com/shabinder/common/di/providers/YoutubeMusic.kt | 2 +- .../kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt | 3 ++- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/shabinder/spotiflyer/App.kt b/android/src/main/java/com/shabinder/spotiflyer/App.kt index cdef2ed9..19c5ea91 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/App.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/App.kt @@ -29,11 +29,12 @@ class App: Application(), KoinComponent { super.onCreate() appContext = this + val loggingEnabled = true - initKoin { + initKoin(loggingEnabled) { androidLogger() androidContext(this@App) - modules(appModule) + modules(appModule(loggingEnabled)) } } } \ No newline at end of file diff --git a/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt b/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt index 3a682f3f..34ff7b9d 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt @@ -21,15 +21,16 @@ import com.tonyodev.fetch2.Fetch import com.tonyodev.fetch2.FetchConfiguration import org.koin.dsl.module -val appModule = module { - single { createFetchInstance() } +fun appModule(enableLogging:Boolean = false) = module { + single { createFetchInstance(enableLogging) } } -private fun createFetchInstance():Fetch{ +private fun createFetchInstance(enableLogging:Boolean = false):Fetch{ val fetchConfiguration = FetchConfiguration.Builder(appContext).run { setNamespace("ForegroundDownloaderService") setDownloadConcurrentLimit(4) + enableLogging(enableLogging) build() } diff --git a/common/dependency-injection/build.gradle.kts b/common/dependency-injection/build.gradle.kts index 5e43ff7a..98089083 100644 --- a/common/dependency-injection/build.gradle.kts +++ b/common/dependency-injection/build.gradle.kts @@ -48,9 +48,9 @@ kotlin { dependencies { implementation(project(":common:data-models")) implementation(project(":common:database")) - implementation("org.jetbrains.kotlinx:atomicfu:0.15.2") + implementation("org.jetbrains.kotlinx:atomicfu:0.16.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") - implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0") implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0") implementation(Ktor.clientCore) implementation(Ktor.clientSerialization) diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt index 6f7ef219..38856d00 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt @@ -25,10 +25,11 @@ import io.ktor.client.HttpClient class YoutubeMp3( override val httpClient: HttpClient, - private val logger: Kermit, + override val logger: Kermit, private val dir: Dir, ) : Yt1sMp3 { suspend fun getMp3DownloadLink(videoID: String): String? = getLinkFromYt1sMp3(videoID)?.let { + logger.i { "Download Link: $it" } if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) "https://kind-grasshopper-73.telebit.io/cors/$it" // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt index a1acf12d..9cf1eb05 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt @@ -20,7 +20,7 @@ import co.touchlab.kermit.Kermit import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.YoutubeTrack -import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch +import com.shabinder.fuzzywuzzy.diffutils.FuzzySearch import io.ktor.client.HttpClient import io.ktor.client.request.headers import io.ktor.client.request.post diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt index 898aba06..4a1613a5 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt @@ -16,6 +16,7 @@ package com.shabinder.common.di.youtubeMp3 +import co.touchlab.kermit.Kermit import com.shabinder.common.di.gaana.corsApi import io.ktor.client.HttpClient import io.ktor.client.request.forms.FormDataContent @@ -31,7 +32,7 @@ import kotlinx.serialization.json.jsonPrimitive interface Yt1sMp3 { val httpClient: HttpClient - + val logger: Kermit /* * Downloadable Mp3 Link for YT videoID. * */ From 149c9aceb3eff8662f03bbee06d1f0399fd2e800 Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 28 Apr 2021 19:08:08 +0530 Subject: [PATCH 2/6] android DM Ktor --- .../main/java/com/shabinder/spotiflyer/App.kt | 3 +- .../com/shabinder/common/di/AndroidDir.kt | 93 +++++++++++-------- .../common/di/worker/ForegroundService.kt | 89 +++++++++++++++--- .../kotlin/com/shabinder/common/di/Dir.kt | 3 +- .../common/di/utils/ParallelExecutor.kt | 3 +- 5 files changed, 137 insertions(+), 54 deletions(-) diff --git a/android/src/main/java/com/shabinder/spotiflyer/App.kt b/android/src/main/java/com/shabinder/spotiflyer/App.kt index 19c5ea91..feb0d934 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/App.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/App.kt @@ -23,6 +23,7 @@ import com.shabinder.spotiflyer.di.appModule import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.component.KoinComponent +import org.koin.core.logger.Level class App: Application(), KoinComponent { override fun onCreate() { @@ -32,7 +33,7 @@ class App: Application(), KoinComponent { val loggingEnabled = true initKoin(loggingEnabled) { - androidLogger() + androidLogger(Level.NONE) androidContext(this@App) modules(appModule(loggingEnabled)) } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt index 595fab55..a4b4bc6a 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt @@ -23,6 +23,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.media.MediaScannerConnection import android.os.Environment +import android.widget.Toast import androidx.compose.ui.graphics.asImageBitmap import co.touchlab.kermit.Kermit import com.mpatric.mp3agic.Mp3File @@ -93,53 +94,65 @@ actual class Dir actual constructor( ) { withContext(Dispatchers.IO){ val songFile = File(trackDetails.outputFilePath) - /* - * Check , if Fetch was Used, File is saved Already, else write byteArray we Received - * */ - // if(!m4aFile.exists()) m4aFile.writeBytes(mp3ByteArray) + try { + /* + * Check , if Fetch was Used, File is saved Already, else write byteArray we Received + * */ + if(!songFile.exists()) { + /*Make intermediate Dirs if they don't exist yet*/ + songFile.parentFile.mkdirs() + } - when (trackDetails.outputFilePath.substringAfterLast('.')) { - ".mp3" -> { - Mp3File(File(songFile.absolutePath)) - .removeAllTags() - .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails) - addToLibrary(songFile.absolutePath) - } - ".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 -> { - try { + if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray) + + when (trackDetails.outputFilePath.substringAfterLast('.')) { + ".mp3" -> { Mp3File(File(songFile.absolutePath)) .removeAllTags() .setId3v1Tags(trackDetails) .setId3v2TagsAndSaveFile(trackDetails) addToLibrary(songFile.absolutePath) - } catch (e: Exception) { e.printStackTrace() } + } + ".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 -> { + try { + Mp3File(File(songFile.absolutePath)) + .removeAllTags() + .setId3v1Tags(trackDetails) + .setId3v2TagsAndSaveFile(trackDetails) + addToLibrary(songFile.absolutePath) + } catch (e: Exception) { e.printStackTrace() } + } } + }catch (e:Exception){ + withContext(Dispatchers.Main){ + Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() + } + logger.e { "${songFile.absolutePath} could not be created" } } } } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt index d06d88e7..657099bf 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt @@ -38,10 +38,9 @@ import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.net.toUri import co.touchlab.kermit.Kermit -import com.shabinder.common.di.Dir -import com.shabinder.common.di.FetchPlatformQueryResult -import com.shabinder.common.di.R -import com.shabinder.common.di.getData +import com.shabinder.common.di.* +import com.shabinder.common.di.utils.ParallelExecutor +import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import com.shabinder.downloader.YoutubeDownloader @@ -59,6 +58,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import java.io.File @@ -80,14 +80,14 @@ class ForegroundService : Service(), CoroutineScope { override val coroutineContext: CoroutineContext get() = serviceJob + Dispatchers.IO - private val requestMap = hashMapOf() + //private val requestMap = hashMapOf() private val allTracksStatus = hashMapOf() private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false private var messageList = mutableListOf("", "", "", "", "") private lateinit var cancelIntent: PendingIntent private lateinit var downloadManager: DownloadManager - + private lateinit var downloadService: ParallelExecutor private val fetcher: FetchPlatformQueryResult by inject() private val logger: Kermit by inject() private val fetch: Fetch by inject() @@ -101,6 +101,7 @@ class ForegroundService : Service(), CoroutineScope { override fun onCreate() { super.onCreate() serviceJob = SupervisorJob() + downloadService = ParallelExecutor(Dispatchers.IO) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel(channelId, "Downloader Service") } @@ -110,7 +111,7 @@ class ForegroundService : Service(), CoroutineScope { ).apply { action = "kill" } cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT) downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - fetch.removeAllListeners().addListener(fetchListener) + //fetch.removeAllListeners().addListener(fetchListener) } @SuppressLint("WakelockTimeout") @@ -209,7 +210,72 @@ class ForegroundService : Service(), CoroutineScope { } private fun enqueueDownload(url: String, track: TrackDetails) { - val request = Request(url, track.outputFilePath).apply { + // Initiating Download + addToNotification("Downloading ${track.title}") + logger.d(tag) { "${track.title} Download Started" } + allTracksStatus[track.title] = DownloadStatus.Downloading() + sendTrackBroadcast(Status.DOWNLOADING.name, track) + + // Enqueueing Download + launch { + downloadService.execute { + downloadFile(url).collect { + when (it) { + 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}") + downloaded++ + } + updateNotification() + sendTrackBroadcast(Status.FAILED.name,track) + } + + is DownloadResult.Progress -> { + allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress) + logger.d(tag) { "${track.title} Progress: ${it.progress} %" } + + val intent = Intent().apply { + action = "Progress" + putExtra("progress", it.progress) + putExtra("track", track) + } + sendBroadcast(intent) + } + + is DownloadResult.Success -> { // Todo clear map + try { + // Save File and Embed Metadata + val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) } + allTracksStatus[track.title] = DownloadStatus.Converting + sendTrackBroadcast("Converting", track) + addToNotification("Processing ${track.title}") + job.invokeOnCompletion { _ -> + converted++ + allTracksStatus[track.title] = DownloadStatus.Downloaded + sendTrackBroadcast(Status.COMPLETED.name, track) + removeFromNotification("Processing ${track.title}") + } + logger.d(tag) { "${track.title} Download Completed" } + } catch ( + e: KotlinNullPointerException + ) { + // Try downloading using android DM + logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" } + logger.d(tag) { "${track.title} Requesting Download thru Android DM" } + downloadUsingDM(url, track.outputFilePath, track) + } + downloaded++ + removeFromNotification("Downloading ${track.title}") + } + } + } + } + } + + /* val request = Request(url, track.outputFilePath).apply { priority = Priority.NORMAL networkType = NetworkType.ALL } @@ -222,13 +288,13 @@ class ForegroundService : Service(), CoroutineScope { { error -> logger.d(tag) { "Enqueuing Error:${error.throwable}" } } - ) + )*/ } /** * Fetch Listener/ Responsible for Fetch Behaviour **/ - private var fetchListener: FetchListener = object : FetchListener { + /*private var fetchListener: FetchListener = object : FetchListener { override fun onQueued( download: Download, waitingOnNetwork: Boolean @@ -348,7 +414,7 @@ class ForegroundService : Service(), CoroutineScope { } } } - } + }*/ /** * If fetch Fails , Android Download Manager To RESCUE!! @@ -450,6 +516,7 @@ class ForegroundService : Service(), CoroutineScope { messageList = mutableListOf("Cleaning And Exiting", "", "", "", "") fetch.cancelAll() fetch.removeAll() + downloadService.close() updateNotification() cleanFiles(File(dir.defaultDir())) // TODO cleanFiles(File(dir.imageCacheDir())) diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt index d567e376..96d27ad0 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt @@ -58,7 +58,8 @@ suspend fun downloadFile(url: String): Flow { val data = ByteArray(response.contentLength()!!.toInt()) var offset = 0 do { - val currentRead = response.content.readAvailable(data, offset, data.size) + // Set Length optimally, after how many kb you want a progress update, now it 0.25mb + val currentRead = response.content.readAvailable(data, offset, 250000) offset += currentRead val progress = (offset * 100f / data.size).roundToInt() emit(DownloadResult.Progress(progress)) diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt index 48687115..4a71fad1 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt @@ -21,6 +21,7 @@ package com.shabinder.common.di.utils // implementation("org.jetbrains.kotlinx:atomicfu:0.14.4") // Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e +import com.shabinder.common.di.dispatcherIO import io.ktor.utils.io.core.Closeable import kotlinx.atomicfu.atomic import kotlinx.coroutines.CancellationException @@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext class ParallelExecutor( - parentContext: CoroutineContext, + parentContext: CoroutineContext = dispatcherIO, ) : Closeable { private val concurrentOperationLimit = atomic(4) From 3a30cbcc58b3b79357eb312790ada8472c3dd1f5 Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 28 Apr 2021 21:34:17 +0530 Subject: [PATCH 3/6] Deps-Update and Build Fixes --- buildSrc/buildSrc/src/main/kotlin/Versions.kt | 9 +++++++- .../multiplatform-setup-test.gradle.kts | 13 +++++++++++- .../kotlin/multiplatform-setup.gradle.kts | 21 +++++++++++++------ common/data-models/build.gradle.kts | 1 - common/database/build.gradle.kts | 9 ++++---- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/buildSrc/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/buildSrc/src/main/kotlin/Versions.kt index a9df79c4..2e21b160 100644 --- a/buildSrc/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/buildSrc/src/main/kotlin/Versions.kt @@ -28,7 +28,7 @@ object Versions { const val ktLint = "10.0.0" // DI - const val koin = "3.0.1-beta-1" + const val koin = "3.0.1" // Logger const val kermit = "0.1.8" @@ -50,6 +50,13 @@ object Versions { const val targetSdkVersion = 29 const val androidLifecycle = "2.3.0" } +object HostOS { + // Host OS Properties + private val hostOs = System.getProperty("os.name") + val isMingwX64 = hostOs.startsWith("Windows",true) + val isMac = hostOs.startsWith("Mac",true) + val isLinux = hostOs.startsWith("Linux",true) +} object Koin { val core = "io.insert-koin:koin-core:${Versions.koin}" val test = "io.insert-koin:koin-test:${Versions.koin}" diff --git a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts index 74ed07e4..d6ec5b69 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts @@ -21,6 +21,18 @@ plugins { } kotlin { + + /*IOS Target Can be only built on Mac*/ + if(HostOS.isMac){ + val sdkName: String? = System.getenv("SDK_NAME") + val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos") + if (isiOSDevice) { + iosArm64("ios") + } else { + iosX64("ios") + } + } + jvm("desktop").compilations.all { kotlinOptions { useIR = true @@ -40,7 +52,6 @@ kotlin { // nodejs() binaries.executable() } - ios() sourceSets { named("commonTest") { dependencies { diff --git a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts index 3eb38da4..0044a427 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts @@ -23,12 +23,15 @@ plugins { kotlin { - val sdkName: String? = System.getenv("SDK_NAME") - val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos") - if (isiOSDevice) { - iosArm64("ios") - } else { - iosX64("ios") + /*IOS Target Can be only built on Mac*/ + if(HostOS.isMac){ + val sdkName: String? = System.getenv("SDK_NAME") + val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos") + if (isiOSDevice) { + iosArm64("ios") + } else { + iosX64("ios") + } } jvm("desktop").compilations.all { @@ -50,6 +53,7 @@ kotlin { // nodejs() binaries.executable() } + sourceSets { named("commonMain") { dependencies {} @@ -86,6 +90,11 @@ kotlin { implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.148-kotlin-1.4.30") } } + if(HostOS.isMac){ + named("iosMain"){ + dependencies { } + } + } } tasks.withType { diff --git a/common/data-models/build.gradle.kts b/common/data-models/build.gradle.kts index 3831ca5b..149e0fa0 100644 --- a/common/data-models/build.gradle.kts +++ b/common/data-models/build.gradle.kts @@ -30,6 +30,5 @@ kotlin { api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") } } - val iosMain by getting } } diff --git a/common/database/build.gradle.kts b/common/database/build.gradle.kts index ded9c84b..9b0652c0 100644 --- a/common/database/build.gradle.kts +++ b/common/database/build.gradle.kts @@ -50,10 +50,11 @@ kotlin { implementation(SqlDelight.jdbcDriver) } } - - val iosMain by getting { - dependencies { - implementation(SqlDelight.nativeDriver) + if(HostOS.isMac){ + val iosMain by getting { + dependencies { + implementation(SqlDelight.nativeDriver) + } } } } From a0797bd891318d27808eb6f46ae82b642aa148c0 Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 28 Apr 2021 22:04:13 +0530 Subject: [PATCH 4/6] Fetch Lib Removed --- android/build.gradle.kts | 20 +-- .../com/shabinder/spotiflyer/MainActivity.kt | 2 +- .../com/shabinder/spotiflyer/di/AppModule.kt | 24 +-- .../com/shabinder/common/models/Status.kt | 64 +++++++ common/dependency-injection/build.gradle.kts | 3 +- .../common/di/worker/ForegroundService.kt | 170 +----------------- 6 files changed, 76 insertions(+), 207 deletions(-) create mode 100644 common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Status.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index dc5be589..2e027593 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -106,31 +106,19 @@ dependencies { implementation("com.google.accompanist:accompanist-insets:0.7.1") - //DECOMPOSE + // DECOMPOSE implementation(Decompose.decompose) implementation(Decompose.extensionsCompose) - //Firebase + // Firebase implementation(platform("com.google.firebase:firebase-bom:27.0.0")) implementation("com.google.firebase:firebase-analytics-ktx") implementation("com.google.firebase:firebase-crashlytics-ktx") implementation("com.google.firebase:firebase-perf-ktx") -/* - //Lifecycle - Versions.androidLifecycle.let{ - implementation("androidx.lifecycle:lifecycle-runtime-ktx:$it") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:$it") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$it") - implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$it") - } -*/ - - Extras.Android.apply { implementation(appUpdator) implementation(razorpay) - implementation(fetch) } implementation(MVIKotlin.mvikotlin) implementation(MVIKotlin.mvikotlinMain) @@ -139,11 +127,11 @@ dependencies { implementation(Decompose.decompose) implementation(Decompose.extensionsCompose) - //Test + // Test testImplementation("junit:junit:4.13.2") androidTestImplementation(Androidx.junit) androidTestImplementation(Androidx.expresso) - //Desugaring + // Desugaring coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") } \ No newline at end of file diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index b226b846..d5307a80 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -66,7 +66,7 @@ import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.uikit.* import com.shabinder.spotiflyer.utils.* -import com.tonyodev.fetch2.Status +import com.shabinder.common.models.Status import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.koin.android.ext.android.inject diff --git a/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt b/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt index 34ff7b9d..54077069 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/di/AppModule.kt @@ -16,28 +16,6 @@ package com.shabinder.spotiflyer.di -import com.shabinder.common.database.appContext -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.FetchConfiguration import org.koin.dsl.module -fun appModule(enableLogging:Boolean = false) = module { - single { createFetchInstance(enableLogging) } -} - -private fun createFetchInstance(enableLogging:Boolean = false):Fetch{ - val fetchConfiguration = - FetchConfiguration.Builder(appContext).run { - setNamespace("ForegroundDownloaderService") - setDownloadConcurrentLimit(4) - enableLogging(enableLogging) - build() - } - - return Fetch.run { - setDefaultInstanceConfiguration(fetchConfiguration) - getDefaultInstance() - }.apply { - removeAll() //Starting fresh - } -} \ No newline at end of file +fun appModule(enableLogging:Boolean = false) = module {} diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Status.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Status.kt new file mode 100644 index 00000000..a133732d --- /dev/null +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Status.kt @@ -0,0 +1,64 @@ +package com.shabinder.common.models + +import kotlin.jvm.JvmStatic + +/** + * Enumeration which contains the different states a download + * could go through. + * + * From Fetch + * */ +enum class Status constructor(val value: Int) { + + /** Indicates when a download is newly created and not yet queued.*/ + NONE(0), + + /** Indicates when a newly created download is queued.*/ + QUEUED(1), + + /** Indicates when a download is currently being downloaded.*/ + DOWNLOADING(2), + + /** Indicates when a download is paused.*/ + PAUSED(3), + + /** Indicates when a download is completed.*/ + COMPLETED(4), + + /** Indicates when a download is cancelled.*/ + CANCELLED(5), + + /** Indicates when a download has failed.*/ + FAILED(6), + + /** Indicates when a download has been removed and is no longer managed by Fetch.*/ + REMOVED(7), + + /** Indicates when a download has been deleted and is no longer managed by Fetch.*/ + DELETED(8), + + /** Indicates when a download has been Added to Fetch for management.*/ + ADDED(9); + + companion object { + + @JvmStatic + fun valueOf(value: Int): Status { + return when (value) { + 0 -> NONE + 1 -> QUEUED + 2 -> DOWNLOADING + 3 -> PAUSED + 4 -> COMPLETED + 5 -> CANCELLED + 6 -> FAILED + 7 -> REMOVED + 8 -> DELETED + 9 -> ADDED + else -> NONE + } + } + + } + +} \ No newline at end of file diff --git a/common/dependency-injection/build.gradle.kts b/common/dependency-injection/build.gradle.kts index 98089083..1ffb4c60 100644 --- a/common/dependency-injection/build.gradle.kts +++ b/common/dependency-injection/build.gradle.kts @@ -20,7 +20,7 @@ plugins { id("multiplatform-setup") id("android-setup") kotlin("plugin.serialization") - kotlin("native.cocoapods") //version "1.4.32" + kotlin("native.cocoapods") } version = "1.0" @@ -69,7 +69,6 @@ kotlin { implementation(compose.materialIconsExtended) implementation(Koin.android) implementation(Ktor.clientAndroid) - implementation(Extras.Android.fetch) implementation(Extras.Android.razorpay) api(Extras.mp3agic) api(Extras.jaudioTagger) diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt index 657099bf..bd682a91 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt @@ -45,15 +45,7 @@ import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import com.shabinder.downloader.YoutubeDownloader import com.shabinder.downloader.models.formats.Format -import com.tonyodev.fetch2.Download -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.FetchListener -import com.tonyodev.fetch2.NetworkType -import com.tonyodev.fetch2.Priority -import com.tonyodev.fetch2.Request -import com.tonyodev.fetch2.Status -import com.tonyodev.fetch2core.DownloadBlock +import com.shabinder.common.models.Status import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -80,7 +72,6 @@ class ForegroundService : Service(), CoroutineScope { override val coroutineContext: CoroutineContext get() = serviceJob + Dispatchers.IO - //private val requestMap = hashMapOf() private val allTracksStatus = hashMapOf() private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false @@ -90,7 +81,6 @@ class ForegroundService : Service(), CoroutineScope { private lateinit var downloadService: ParallelExecutor private val fetcher: FetchPlatformQueryResult by inject() private val logger: Kermit by inject() - private val fetch: Fetch by inject() private val dir: Dir by inject() private val ytDownloader: YoutubeDownloader get() = fetcher.youtubeProvider.ytDownloader @@ -111,7 +101,6 @@ class ForegroundService : Service(), CoroutineScope { ).apply { action = "kill" } cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT) downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - //fetch.removeAllListeners().addListener(fetchListener) } @SuppressLint("WakelockTimeout") @@ -245,14 +234,14 @@ class ForegroundService : Service(), CoroutineScope { sendBroadcast(intent) } - is DownloadResult.Success -> { // Todo clear map + is DownloadResult.Success -> { try { // Save File and Embed Metadata val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) } allTracksStatus[track.title] = DownloadStatus.Converting sendTrackBroadcast("Converting", track) addToNotification("Processing ${track.title}") - job.invokeOnCompletion { _ -> + job.invokeOnCompletion { converted++ allTracksStatus[track.title] = DownloadStatus.Downloaded sendTrackBroadcast(Status.COMPLETED.name, track) @@ -274,152 +263,12 @@ class ForegroundService : Service(), CoroutineScope { } } } - - /* val request = Request(url, track.outputFilePath).apply { - priority = Priority.NORMAL - networkType = NetworkType.ALL - } - fetch.enqueue( - request, - { request1 -> - requestMap[request1] = track - logger.d(tag) { "Enqueuing Download" } - }, - { error -> - logger.d(tag) { "Enqueuing Error:${error.throwable}" } - } - )*/ } - /** - * Fetch Listener/ Responsible for Fetch Behaviour - **/ - /*private var fetchListener: FetchListener = object : FetchListener { - override fun onQueued( - download: Download, - waitingOnNetwork: Boolean - ) { - requestMap[download.request]?.let { sendTrackBroadcast(Status.QUEUED.name, it) } - } - - override fun onRemoved(download: Download) { - // TODO("Not yet implemented") - } - - override fun onResumed(download: Download) { - // TODO("Not yet implemented") - } - - override fun onStarted( - download: Download, - downloadBlocks: List, - totalBlocks: Int - ) { - launch { - val track = requestMap[download.request] - addToNotification("Downloading ${track?.title}") - logger.d(tag) { "${track?.title} Download Started" } - track?.let { - allTracksStatus[it.title] = DownloadStatus.Downloading() - sendTrackBroadcast(Status.DOWNLOADING.name, track) - } - } - } - - override fun onWaitingNetwork(download: Download) { - // TODO("Not yet implemented") - } - - override fun onAdded(download: Download) { - // TODO("Not yet implemented") - } - - override fun onCancelled(download: Download) { - // TODO("Not yet implemented") - } - - override fun onCompleted(download: Download) { - val track = requestMap[download.request] - try { - track?.let { - val job = launch { dir.saveFileWithMetadata(byteArrayOf(), it) } - allTracksStatus[it.title] = DownloadStatus.Converting - sendTrackBroadcast("Converting", it) - addToNotification("Processing ${it.title}") - job.invokeOnCompletion { _ -> - converted++ - allTracksStatus[it.title] = DownloadStatus.Downloaded - sendTrackBroadcast(Status.COMPLETED.name, it) - removeFromNotification("Processing ${it.title}") - } - } - logger.d(tag) { "${track?.title} Download Completed" } - } catch ( - e: KotlinNullPointerException - ) { - logger.d(tag) { "${track?.title} Download Failed! Error:Fetch!!!!" } - logger.d(tag) { "${track?.title} Requesting Download thru Android DM" } - downloadUsingDM(download.request.url, download.request.file, track!!) - } - downloaded++ - requestMap.remove(download.request) - removeFromNotification("Downloading ${track?.title}") - } - - override fun onDeleted(download: Download) { - // TODO("Not yet implemented") - } - - override fun onDownloadBlockUpdated( - download: Download, - downloadBlock: DownloadBlock, - totalBlocks: Int - ) { - // TODO("Not yet implemented") - } - - override fun onError(download: Download, error: Error, throwable: Throwable?) { - launch { - val track = requestMap[download.request] - downloaded++ - logger.d(tag) { download.error.throwable.toString() } - logger.d(tag) { "${track?.title} Requesting Download thru Android DM" } - downloadUsingDM(download.request.url, download.request.file, track!!) - requestMap.remove(download.request) - removeFromNotification("Downloading ${track.title}") - } - updateNotification() - } - - override fun onPaused(download: Download) { - // TODO("Not yet implemented") - } - - override fun onProgress( - download: Download, - etaInMilliSeconds: Long, - downloadedBytesPerSecond: Long - ) { - launch { - requestMap[download.request]?.run { - allTracksStatus[title] = DownloadStatus.Downloading(download.progress) - logger.d(tag) { "$title ETA: ${etaInMilliSeconds / 1000} sec" } - - val intent = Intent().apply { - action = "Progress" - putExtra("progress", download.progress) - putExtra("track", this@run) - } - sendBroadcast(intent) - } - } - } - }*/ - /** * If fetch Fails , Android Download Manager To RESCUE!! **/ - fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails) { + private fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails) { launch { val uri = Uri.parse(url) val request = DownloadManager.Request(uri).apply { @@ -514,8 +363,6 @@ class ForegroundService : Service(), CoroutineScope { launch { logger.d(tag) { "Killing Self" } messageList = mutableListOf("Cleaning And Exiting", "", "", "", "") - fetch.cancelAll() - fetch.removeAll() downloadService.close() updateNotification() cleanFiles(File(dir.defaultDir())) @@ -573,7 +420,7 @@ class ForegroundService : Service(), CoroutineScope { updateNotification() } - fun sendTrackBroadcast(action: String, track: TrackDetails) { + private fun sendTrackBroadcast(action: String, track: TrackDetails) { val intent = Intent().apply { setAction(action) putExtra("track", track) @@ -581,10 +428,3 @@ class ForegroundService : Service(), CoroutineScope { this@ForegroundService.sendBroadcast(intent) } } - -private fun Fetch.removeAllListeners(): Fetch { - for (listener in this.getListenerSet()) { - this.removeListener(listener) - } - return this -} From 318c673cea9cd05652317c3f0c828bd0216014d2 Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 28 Apr 2021 22:52:33 +0530 Subject: [PATCH 5/6] Error Handling --- .../main/java/com/shabinder/spotiflyer/App.kt | 2 +- .../common/uikit/SpotiFlyerListUi.kt | 11 ++++++ .../com/shabinder/common/di/AndroidDir.kt | 3 +- .../common/di/worker/ForegroundService.kt | 13 ++++--- .../kotlin/com/shabinder/common/di/DI.kt | 8 +++++ .../kotlin/com/shabinder/common/di/Dir.kt | 36 ++++++++++--------- .../com/shabinder/common/di/WebActual.kt | 4 --- .../shabinder/common/list/SpotiFlyerList.kt | 3 +- .../list/store/SpotiFlyerListStoreProvider.kt | 19 +++++++--- 9 files changed, 65 insertions(+), 34 deletions(-) diff --git a/android/src/main/java/com/shabinder/spotiflyer/App.kt b/android/src/main/java/com/shabinder/spotiflyer/App.kt index feb0d934..8d0e63fb 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/App.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/App.kt @@ -33,7 +33,7 @@ class App: Application(), KoinComponent { val loggingEnabled = true initKoin(loggingEnabled) { - androidLogger(Level.NONE) + androidLogger(Level.NONE) // No virtual method elapsedNow androidContext(this@App) modules(appModule(loggingEnabled)) } diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt index b4caef19..670311d0 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt @@ -35,6 +35,7 @@ import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -49,6 +50,7 @@ import com.shabinder.common.di.Picture import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails +import kotlinx.coroutines.delay @Composable fun SpotiFlyerListContent( @@ -61,11 +63,20 @@ fun SpotiFlyerListContent( // TODO Better Null Handling val result = model.queryResult if (result == null) { + /* Loading Bar */ Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator() Spacer(modifier.padding(8.dp)) Text("Loading..", style = appNameStyle, color = colorPrimary) } + LaunchedEffect(Unit) { + delay(350) + /*Handle if Any Exception Occurred*/ + model.errorOccurred?.let { + showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection") + component.onBackPressed() + } + } } else { LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt index a4b4bc6a..db8c5172 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt @@ -150,8 +150,9 @@ actual class Dir actual constructor( } }catch (e:Exception){ withContext(Dispatchers.Main){ - Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() + //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" } } } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt index bd682a91..f2600a0d 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt @@ -43,7 +43,6 @@ import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails -import com.shabinder.downloader.YoutubeDownloader import com.shabinder.downloader.models.formats.Format import com.shabinder.common.models.Status import kotlinx.coroutines.CoroutineScope @@ -57,6 +56,7 @@ import java.io.File import kotlin.coroutines.CoroutineContext class ForegroundService : Service(), CoroutineScope { + private val tag: String = "Foreground Service" private val channelId = "ForegroundDownloaderService" private val notificationId = 101 @@ -64,26 +64,25 @@ class ForegroundService : Service(), CoroutineScope { private var converted = 0 // Total Files Converted private var downloaded = 0 // Total Files downloaded private var failed = 0 // Total Files failed - private val isFinished: Boolean - get() = converted + failed == total - private var isSingleDownload: Boolean = false + private val isFinished get() = converted + failed == total + private var isSingleDownload = false private lateinit var serviceJob: Job override val coroutineContext: CoroutineContext get() = serviceJob + Dispatchers.IO private val allTracksStatus = hashMapOf() + private var messageList = mutableListOf("", "", "", "", "") private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false - private var messageList = mutableListOf("", "", "", "", "") private lateinit var cancelIntent: PendingIntent + private lateinit var downloadManager: DownloadManager private lateinit var downloadService: ParallelExecutor + private val ytDownloader get() = fetcher.youtubeProvider.ytDownloader private val fetcher: FetchPlatformQueryResult by inject() private val logger: Kermit by inject() private val dir: Dir by inject() - private val ytDownloader: YoutubeDownloader - get() = fetcher.youtubeProvider.ytDownloader override fun onBind(intent: Intent): IBinder? = null diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt index 837a054b..c48b16b1 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt @@ -24,6 +24,7 @@ import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.YoutubeMp3 import com.shabinder.common.di.providers.YoutubeMusic import io.ktor.client.HttpClient +import io.ktor.client.features.HttpTimeout import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.features.logging.DEFAULT @@ -65,6 +66,12 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri install(JsonFeature) { this.serializer = serializer } + // Timeout + install(HttpTimeout) { + requestTimeoutMillis = 15000L + connectTimeoutMillis = 15000L + socketTimeoutMillis = 15000L + } if (enableNetworkLogs) { install(Logging) { logger = Logger.DEFAULT @@ -72,4 +79,5 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri } } } +/*Client Active Throughout App's Lifetime*/ val ktorHttpClient = HttpClient {} diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt index 96d27ad0..321d28f0 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt @@ -53,23 +53,27 @@ expect class Dir ( suspend fun downloadFile(url: String): Flow { return flow { - val client = createHttpClient() - val response = client.get(url).execute() - val data = ByteArray(response.contentLength()!!.toInt()) - var offset = 0 - do { - // Set Length optimally, after how many kb you want a progress update, now it 0.25mb - val currentRead = response.content.readAvailable(data, offset, 250000) - offset += currentRead - val progress = (offset * 100f / data.size).roundToInt() - emit(DownloadResult.Progress(progress)) - } while (currentRead > 0) - if (response.status.isSuccess()) { - emit(DownloadResult.Success(data)) - } else { - emit(DownloadResult.Error("File not downloaded")) + try { + val client = createHttpClient() + val response = client.get(url).execute() + val data = ByteArray(response.contentLength()!!.toInt()) + var offset = 0 + do { + // Set Length optimally, after how many kb you want a progress update, now it 0.25mb + val currentRead = response.content.readAvailable(data, offset, 250000) + offset += currentRead + val progress = (offset * 100f / data.size).roundToInt() + emit(DownloadResult.Progress(progress)) + } while (currentRead > 0) + if (response.status.isSuccess()) { + emit(DownloadResult.Success(data)) + } else { + emit(DownloadResult.Error("File not downloaded")) + } + client.close() + } catch (e:Exception) { + emit(DownloadResult.Error(e.message ?: "File not downloaded")) } - client.close() } } diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt index 9500391c..44503299 100644 --- a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt @@ -63,10 +63,6 @@ private suspend fun isInternetAvailable(): Boolean { actual val isInternetAvailable: Boolean get() { return true - /*var result = false - val job = GlobalScope.launch { result = isInternetAvailable() } - while(job.isActive){} - return result*/ } val DownloadProgressFlow: MutableSharedFlow> = MutableSharedFlow(1) diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt index 9dc3fb64..54df04e8 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt @@ -73,7 +73,8 @@ interface SpotiFlyerList { data class State( val queryResult: PlatformQueryResult? = null, val link: String = "", - val trackList: List = emptyList() + val trackList: List = emptyList(), + val errorOccurred: Exception? = null ) } diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt index 0fb132f5..3e860ff2 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt @@ -58,6 +58,7 @@ internal class SpotiFlyerListStoreProvider( data class ResultFetched(val result: PlatformQueryResult, val trackList: List) : Result() data class UpdateTrackList(val list: List) : Result() data class UpdateTrackItem(val item: TrackDetails) : Result() + data class ErrorOccurred(val error: Exception) : Result() } private inner class ExecutorImpl : SuspendExecutor() { @@ -74,10 +75,19 @@ internal class SpotiFlyerListStoreProvider( override suspend fun executeIntent(intent: Intent, getState: () -> State) { when (intent) { - is Intent.SearchLink -> fetchQuery.query(link)?.let { result -> - result.trackList = result.trackList.toMutableList() - dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))) - executeIntent(Intent.RefreshTracksStatuses, getState) + is Intent.SearchLink -> { + try { + val result = fetchQuery.query(link) + if( result != null) { + result.trackList = result.trackList.toMutableList() + dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))) + executeIntent(Intent.RefreshTracksStatuses, getState) + } else { + throw Exception("An Error Occurred, Check your Link / Connection") + } + } catch (e:Exception) { + dispatch(Result.ErrorOccurred(e)) + } } is Intent.StartDownloadAll -> { @@ -107,6 +117,7 @@ internal class SpotiFlyerListStoreProvider( is Result.ResultFetched -> copy(queryResult = result.result, trackList = result.trackList, link = link) is Result.UpdateTrackList -> copy(trackList = result.list) is Result.UpdateTrackItem -> updateTrackItem(result.item) + is Result.ErrorOccurred -> copy(errorOccurred = result.error) } private fun State.updateTrackItem(item: TrackDetails): State { From 815eb9ca99a9ec182bbef650efb85fd068f7b348 Mon Sep 17 00:00:00 2001 From: shabinder Date: Thu, 29 Apr 2021 16:19:50 +0530 Subject: [PATCH 6/6] Error Handling / try-catch --- .../common/uikit/SpotiFlyerListUi.kt | 17 +++++++------- .../common/di/providers/GaanaProvider.kt | 12 ++++++---- .../common/di/providers/SpotifyProvider.kt | 22 +++++++++++++++---- .../common/di/providers/YoutubeMp3.kt | 16 +++++++++----- .../common/di/providers/YoutubeMusic.kt | 17 +++++++++----- .../common/di/spotify/SpotifyAuth.kt | 10 ++++++--- 6 files changed, 62 insertions(+), 32 deletions(-) diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt index 670311d0..1fb5828d 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt @@ -59,8 +59,15 @@ fun SpotiFlyerListContent( ) { val model by component.models.collectAsState(SpotiFlyerList.State()) + LaunchedEffect(model.errorOccurred) { + /*Handle if Any Exception Occurred*/ + model.errorOccurred?.let { + showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection") + component.onBackPressed() + } + } + Box(modifier = modifier.fillMaxSize()) { - // TODO Better Null Handling val result = model.queryResult if (result == null) { /* Loading Bar */ @@ -69,14 +76,6 @@ fun SpotiFlyerListContent( Spacer(modifier.padding(8.dp)) Text("Loading..", style = appNameStyle, color = colorPrimary) } - LaunchedEffect(Unit) { - delay(350) - /*Handle if Any Exception Occurred*/ - model.errorOccurred?.let { - showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection") - component.onBackPressed() - } - } } else { LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt index d5da8daa..5b1e30ca 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt @@ -46,10 +46,14 @@ class GaanaProvider( if (type == "Error" || link == "Error") { return null } - return gaanaSearch( - type, - link - ) + return try { + gaanaSearch( + type, + link + ) + } catch (e: Exception) { + null + } } private suspend fun gaanaSearch( diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt index 071ba1d7..800998ee 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt @@ -100,10 +100,24 @@ class SpotifyProvider( return null } - return spotifySearch( - type, - link - ) + return try { + spotifySearch( + type, + link + ) + }catch (e: Exception){ + // Try Reinitialising Client // Handle 401 Token Expiry ,etc Exceptions + authenticateSpotifyClient(true) + // Retry Search + try { + spotifySearch( + type, + link + ) + } catch (e:Exception){ + null + } + } } private suspend fun spotifySearch( diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt index 38856d00..d2cf0cf3 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt @@ -28,11 +28,15 @@ class YoutubeMp3( override val logger: Kermit, private val dir: Dir, ) : Yt1sMp3 { - suspend fun getMp3DownloadLink(videoID: String): String? = getLinkFromYt1sMp3(videoID)?.let { - logger.i { "Download Link: $it" } - if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) - "https://kind-grasshopper-73.telebit.io/cors/$it" - // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue - else it + suspend fun getMp3DownloadLink(videoID: String): String? = try { + getLinkFromYt1sMp3(videoID)?.let { + logger.i { "Download Link: $it" } + if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) + "https://kind-grasshopper-73.telebit.io/cors/$it" + // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue + else it + } + } catch (e: Exception) { + null } } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt index 9cf1eb05..141eb306 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt @@ -47,12 +47,17 @@ class YoutubeMusic constructor( private val tag = "YT Music" suspend fun getYTIDBestMatch(query: String, trackDetails: TrackDetails): String? { - return sortByBestMatch( - getYTTracks(query), - trackName = trackDetails.title, - trackArtists = trackDetails.artists, - trackDurationSec = trackDetails.durationSec - ).keys.firstOrNull() + return try { + sortByBestMatch( + getYTTracks(query), + trackName = trackDetails.title, + trackArtists = trackDetails.artists, + trackDurationSec = trackDetails.durationSec + ).keys.firstOrNull() + } catch (e:Exception) { + // All Internet/Client Related Errors + null + } } private suspend fun getYTTracks(query: String): List { val youtubeTracks = mutableListOf() diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt index 956a0772..39dad364 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt @@ -28,9 +28,13 @@ import io.ktor.client.request.post import io.ktor.http.Parameters suspend fun authenticateSpotify(): TokenData? { - return if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") { - body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") }) - } else null + return try { + if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") { + body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") }) + } else null + }catch (e:Exception) { + null + } } private val spotifyAuthClient by lazy {