mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
Error Handling Improv
This commit is contained in:
parent
979fcc342b
commit
b3abc9c4de
@ -42,6 +42,8 @@ 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.common.models.event.coroutines.SuspendableEvent
|
||||
import com.shabinder.common.models.event.coroutines.failure
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -102,10 +104,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(channelId, "Downloader Service")
|
||||
}
|
||||
val intent = Intent(
|
||||
this,
|
||||
ForegroundService::class.java
|
||||
).apply { action = "kill" }
|
||||
val intent = Intent(this, ForegroundService::class.java).apply { action = "kill" }
|
||||
cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT)
|
||||
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
}
|
||||
@ -119,17 +118,9 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
intent?.let {
|
||||
when (it.action) {
|
||||
"kill" -> killService()
|
||||
"query" -> {
|
||||
val response = Intent().apply {
|
||||
action = "query_result"
|
||||
synchronized(trackStatusFlowMap) {
|
||||
putExtra("tracks", trackStatusFlowMap)
|
||||
}
|
||||
}
|
||||
sendBroadcast(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wake locks and misc tasks from here :
|
||||
return if (isServiceStarted) {
|
||||
// Service Already Started
|
||||
@ -160,16 +151,16 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
|
||||
trackList.forEach {
|
||||
trackStatusFlowMap[it.title] = DownloadStatus.Queued
|
||||
launch(Dispatchers.IO) {
|
||||
launch {
|
||||
downloadService.execute {
|
||||
fetcher.findMp3DownloadLink(it).fold(
|
||||
success = { url ->
|
||||
enqueueDownload(url, it)
|
||||
},
|
||||
failure = { _ ->
|
||||
failure = { error ->
|
||||
failed++
|
||||
updateNotification()
|
||||
trackStatusFlowMap[it.title] = DownloadStatus.Failed
|
||||
trackStatusFlowMap[it.title] = DownloadStatus.Failed(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -180,7 +171,6 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
private suspend fun enqueueDownload(url: String, track: TrackDetails) {
|
||||
// Initiating Download
|
||||
addToNotification("Downloading ${track.title}")
|
||||
logger.d(tag) { "${track.title} Download Started" }
|
||||
trackStatusFlowMap[track.title] = DownloadStatus.Downloading()
|
||||
|
||||
// Enqueueing Download
|
||||
@ -188,26 +178,18 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
logger.d(tag) { it.message }
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
failed++
|
||||
updateNotification()
|
||||
trackStatusFlowMap[track.title] = DownloadStatus.Failed(it.cause ?: Exception(it.message))
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
}
|
||||
|
||||
is DownloadResult.Progress -> {
|
||||
trackStatusFlowMap[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 -> {
|
||||
coroutineScope {
|
||||
try {
|
||||
SuspendableEvent {
|
||||
// Save File and Embed Metadata
|
||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) {} }
|
||||
|
||||
@ -223,11 +205,11 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
}
|
||||
logger.d(tag) { "${track.title} Download Completed" }
|
||||
downloaded++
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}.failure { error ->
|
||||
error.printStackTrace()
|
||||
// Download Failed
|
||||
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
||||
failed++
|
||||
trackStatusFlowMap[track.title] = DownloadStatus.Failed(error)
|
||||
}
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
}
|
||||
|
@ -49,5 +49,5 @@ sealed class DownloadStatus : Parcelable {
|
||||
@Parcelize object Queued : DownloadStatus()
|
||||
@Parcelize object NotDownloaded : DownloadStatus()
|
||||
@Parcelize object Converting : DownloadStatus()
|
||||
@Parcelize object Failed : DownloadStatus()
|
||||
@Parcelize data class Failed(val error: Throwable) : DownloadStatus()
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ sealed class SpotiFlyerException(override val message: String): Exception(messag
|
||||
|
||||
data class FeatureNotImplementedYet(override val message: String = "Feature not yet implemented."): SpotiFlyerException(message)
|
||||
data class NoInternetException(override val message: String = "Check Your Internet Connection"): SpotiFlyerException(message)
|
||||
data class MP3ConversionFailed(override val message: String = "MP3 Converter unreachable, probably BUSY !"): SpotiFlyerException(message)
|
||||
|
||||
data class NoMatchFound(
|
||||
val trackName: String? = null,
|
||||
|
@ -61,7 +61,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
|
||||
single { YoutubeProvider(get(), get(), get()) }
|
||||
single { YoutubeMp3(get(), get()) }
|
||||
single { YoutubeMusic(get(), get(), get(), get(), get()) }
|
||||
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
|
||||
@ThreadLocal
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.shabinder.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.database.DownloadRecordDatabaseQueries
|
||||
import com.shabinder.common.di.audioToMp3.AudioToMp3
|
||||
import com.shabinder.common.di.providers.GaanaProvider
|
||||
@ -45,7 +46,8 @@ class FetchPlatformQueryResult(
|
||||
private val youtubeMusic: YoutubeMusic,
|
||||
private val youtubeMp3: YoutubeMp3,
|
||||
private val audioToMp3: AudioToMp3,
|
||||
val dir: Dir
|
||||
val dir: Dir,
|
||||
val logger: Kermit
|
||||
) {
|
||||
private val db: DownloadRecordDatabaseQueries?
|
||||
get() = dir.db?.downloadRecordDatabaseQueries
|
||||
@ -120,7 +122,7 @@ class FetchPlatformQueryResult(
|
||||
trackName = track.title,
|
||||
trackArtists = track.artists
|
||||
).flatMapError { saavnError ->
|
||||
// Lets Try Fetching Now From Youtube Music
|
||||
// Saavn Failed, Lets Try Fetching Now From Youtube Music
|
||||
youtubeMusic.findMp3SongDownloadURLYT(track).flatMapError { ytMusicError ->
|
||||
// If Both Failed Bubble the Exception Up with both StackTraces
|
||||
SuspendableEvent.error(
|
||||
|
@ -2,8 +2,10 @@ package com.shabinder.common.di.audioToMp3
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.SpotiFlyerException
|
||||
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.*
|
||||
@ -31,8 +33,9 @@ interface AudioToMp3 {
|
||||
URL: String,
|
||||
audioQuality: AudioQuality = AudioQuality.getQuality(URL.substringBeforeLast(".").takeLast(3)),
|
||||
): SuspendableEvent<String,Throwable> = SuspendableEvent {
|
||||
val activeHost by getHost() // ex - https://hostveryfast.onlineconverter.com/file/send
|
||||
val jobLink by convertRequest(URL, activeHost, audioQuality) // ex - https://www.onlineconverter.com/convert/309a0f2bbaeb5687b04f96b6d65b47bfdd
|
||||
// Active Host ex - https://hostveryfast.onlineconverter.com/file/send
|
||||
// Convert Job Request ex - https://www.onlineconverter.com/convert/309a0f2bbaeb5687b04f96b6d65b47bfdd
|
||||
var (activeHost,jobLink) = convertRequest(URL, audioQuality).value
|
||||
|
||||
// (jobStatus.contains("d")) == COMPLETION
|
||||
var jobStatus: String
|
||||
@ -44,13 +47,22 @@ interface AudioToMp3 {
|
||||
"${activeHost.removeSuffix("send")}${jobLink.substringAfterLast("/")}"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if(e is ClientRequestException && e.response.status.value == 404) {
|
||||
// No Need to Retry, Host/Converter is Busy
|
||||
throw SpotiFlyerException.MP3ConversionFailed()
|
||||
}
|
||||
// Try Using New Host/Converter
|
||||
convertRequest(URL, audioQuality).value.also {
|
||||
activeHost = it.first
|
||||
jobLink = it.second
|
||||
}
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
retryCount--
|
||||
logger.i("Job Status") { jobStatus }
|
||||
if (!jobStatus.contains("d")) delay(400) // Add Delay , to give Server Time to process audio
|
||||
} while (!jobStatus.contains("d", true) && retryCount != 0)
|
||||
if (!jobStatus.contains("d")) delay(600) // Add Delay , to give Server Time to process audio
|
||||
} while (!jobStatus.contains("d", true) && retryCount > 0)
|
||||
|
||||
"${activeHost.removeSuffix("send")}${jobLink.substringAfterLast("/")}/download"
|
||||
}
|
||||
@ -61,11 +73,10 @@ interface AudioToMp3 {
|
||||
* */
|
||||
private suspend fun convertRequest(
|
||||
URL: String,
|
||||
host: String? = null,
|
||||
audioQuality: AudioQuality = AudioQuality.KBPS160,
|
||||
): SuspendableEvent<String,Throwable> = SuspendableEvent {
|
||||
val activeHost = host ?: getHost().value
|
||||
val res = client.submitFormWithBinaryData<String>(
|
||||
): SuspendableEvent<Pair<String,String>,Throwable> = SuspendableEvent {
|
||||
val activeHost by getHost()
|
||||
val convertJob = client.submitFormWithBinaryData<String>(
|
||||
url = activeHost,
|
||||
formData = formData {
|
||||
append("class", "audio")
|
||||
@ -86,14 +97,14 @@ interface AudioToMp3 {
|
||||
dropLast(3) // last 3 are useless unicode char
|
||||
}
|
||||
|
||||
val job = client.get<HttpStatement>(res) {
|
||||
val job = client.get<HttpStatement>(convertJob) {
|
||||
headers {
|
||||
header("Host", "www.onlineconverter.com")
|
||||
}
|
||||
}.execute()
|
||||
logger.i("Schedule Conversion Job") { job.status.isSuccess().toString() }
|
||||
|
||||
res
|
||||
Pair(activeHost,convertJob)
|
||||
}
|
||||
|
||||
// Active Host free to process conversion
|
||||
|
@ -21,7 +21,6 @@ import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
|
||||
import com.arkivanov.mvikotlin.core.store.Store
|
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory
|
||||
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||
import com.shabinder.common.database.getLogger
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.downloadTracks
|
||||
@ -42,8 +41,6 @@ internal class SpotiFlyerListStoreProvider(
|
||||
private val link: String,
|
||||
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
) {
|
||||
val logger = getLogger()
|
||||
|
||||
fun provide(): SpotiFlyerListStore =
|
||||
object :
|
||||
SpotiFlyerListStore,
|
||||
@ -70,7 +67,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
|
||||
dir.db?.downloadRecordDatabaseQueries?.getLastInsertId()?.executeAsOneOrNull()?.also {
|
||||
// See if It's Time we can request for support for maintaining this project or not
|
||||
logger.d(message = "Database List Last ID: $it", tag = "Database Last ID")
|
||||
fetchQuery.logger.d(message = { "Database List Last ID: $it" }, tag = "Database Last ID")
|
||||
val offset = dir.getDonationOffset
|
||||
dispatch(
|
||||
Result.AskForSupport(
|
||||
|
Loading…
Reference in New Issue
Block a user