Error Handling Improv

This commit is contained in:
shabinder 2021-06-22 12:53:18 +05:30
parent 979fcc342b
commit b3abc9c4de
7 changed files with 42 additions and 49 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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