Error Handling

This commit is contained in:
shabinder 2021-04-28 22:52:33 +05:30
parent a0797bd891
commit 318c673cea
9 changed files with 65 additions and 34 deletions

View File

@ -33,7 +33,7 @@ class App: Application(), KoinComponent {
val loggingEnabled = true val loggingEnabled = true
initKoin(loggingEnabled) { initKoin(loggingEnabled) {
androidLogger(Level.NONE) androidLogger(Level.NONE) // No virtual method elapsedNow
androidContext(this@App) androidContext(this@App)
modules(appModule(loggingEnabled)) modules(appModule(loggingEnabled))
} }

View File

@ -35,6 +35,7 @@ import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment 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.list.SpotiFlyerList
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.delay
@Composable @Composable
fun SpotiFlyerListContent( fun SpotiFlyerListContent(
@ -61,11 +63,20 @@ fun SpotiFlyerListContent(
// TODO Better Null Handling // TODO Better Null Handling
val result = model.queryResult val result = model.queryResult
if (result == null) { if (result == null) {
/* Loading Bar */
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator() CircularProgressIndicator()
Spacer(modifier.padding(8.dp)) Spacer(modifier.padding(8.dp))
Text("Loading..", style = appNameStyle, color = colorPrimary) 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 { } else {
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),

View File

@ -150,8 +150,9 @@ actual class Dir actual constructor(
} }
}catch (e:Exception){ }catch (e:Exception){
withContext(Dispatchers.Main){ 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" } logger.e { "${songFile.absolutePath} could not be created" }
} }
} }

View File

@ -43,7 +43,6 @@ 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.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import com.shabinder.downloader.models.formats.Format import com.shabinder.downloader.models.formats.Format
import com.shabinder.common.models.Status import com.shabinder.common.models.Status
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -57,6 +56,7 @@ import java.io.File
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class ForegroundService : Service(), CoroutineScope { class ForegroundService : Service(), CoroutineScope {
private val tag: String = "Foreground Service" private val tag: String = "Foreground Service"
private val channelId = "ForegroundDownloaderService" private val channelId = "ForegroundDownloaderService"
private val notificationId = 101 private val notificationId = 101
@ -64,26 +64,25 @@ class ForegroundService : Service(), CoroutineScope {
private var converted = 0 // Total Files Converted private var converted = 0 // Total Files Converted
private var downloaded = 0 // Total Files downloaded private var downloaded = 0 // Total Files downloaded
private var failed = 0 // Total Files failed private var failed = 0 // Total Files failed
private val isFinished: Boolean private val isFinished get() = converted + failed == total
get() = converted + failed == total private var isSingleDownload = false
private var isSingleDownload: Boolean = false
private lateinit var serviceJob: Job private lateinit var serviceJob: Job
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = serviceJob + Dispatchers.IO get() = serviceJob + Dispatchers.IO
private val allTracksStatus = hashMapOf<String, DownloadStatus>() private val allTracksStatus = hashMapOf<String, DownloadStatus>()
private var messageList = mutableListOf("", "", "", "", "")
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false private var isServiceStarted = false
private var messageList = mutableListOf("", "", "", "", "")
private lateinit var cancelIntent: PendingIntent private lateinit var cancelIntent: PendingIntent
private lateinit var downloadManager: DownloadManager private lateinit var downloadManager: DownloadManager
private lateinit var downloadService: ParallelExecutor private lateinit var downloadService: ParallelExecutor
private val ytDownloader get() = fetcher.youtubeProvider.ytDownloader
private val fetcher: FetchPlatformQueryResult by inject() private val fetcher: FetchPlatformQueryResult by inject()
private val logger: Kermit by inject() private val logger: Kermit by inject()
private val dir: Dir by inject() private val dir: Dir by inject()
private val ytDownloader: YoutubeDownloader
get() = fetcher.youtubeProvider.ytDownloader
override fun onBind(intent: Intent): IBinder? = null override fun onBind(intent: Intent): IBinder? = null

View File

@ -24,6 +24,7 @@ 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 io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.HttpTimeout
import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT import io.ktor.client.features.logging.DEFAULT
@ -65,6 +66,12 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri
install(JsonFeature) { install(JsonFeature) {
this.serializer = serializer this.serializer = serializer
} }
// Timeout
install(HttpTimeout) {
requestTimeoutMillis = 15000L
connectTimeoutMillis = 15000L
socketTimeoutMillis = 15000L
}
if (enableNetworkLogs) { if (enableNetworkLogs) {
install(Logging) { install(Logging) {
logger = Logger.DEFAULT logger = Logger.DEFAULT
@ -72,4 +79,5 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri
} }
} }
} }
/*Client Active Throughout App's Lifetime*/
val ktorHttpClient = HttpClient {} val ktorHttpClient = HttpClient {}

View File

@ -53,23 +53,27 @@ expect class Dir (
suspend fun downloadFile(url: String): Flow<DownloadResult> { suspend fun downloadFile(url: String): Flow<DownloadResult> {
return flow { return flow {
val client = createHttpClient() try {
val response = client.get<HttpStatement>(url).execute() val client = createHttpClient()
val data = ByteArray(response.contentLength()!!.toInt()) val response = client.get<HttpStatement>(url).execute()
var offset = 0 val data = ByteArray(response.contentLength()!!.toInt())
do { var offset = 0
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb do {
val currentRead = response.content.readAvailable(data, offset, 250000) // Set Length optimally, after how many kb you want a progress update, now it 0.25mb
offset += currentRead val currentRead = response.content.readAvailable(data, offset, 250000)
val progress = (offset * 100f / data.size).roundToInt() offset += currentRead
emit(DownloadResult.Progress(progress)) val progress = (offset * 100f / data.size).roundToInt()
} while (currentRead > 0) emit(DownloadResult.Progress(progress))
if (response.status.isSuccess()) { } while (currentRead > 0)
emit(DownloadResult.Success(data)) if (response.status.isSuccess()) {
} else { emit(DownloadResult.Success(data))
emit(DownloadResult.Error("File not downloaded")) } else {
emit(DownloadResult.Error("File not downloaded"))
}
client.close()
} catch (e:Exception) {
emit(DownloadResult.Error(e.message ?: "File not downloaded"))
} }
client.close()
} }
} }

View File

@ -63,10 +63,6 @@ private suspend fun isInternetAvailable(): Boolean {
actual val isInternetAvailable: Boolean actual val isInternetAvailable: Boolean
get() { get() {
return true return true
/*var result = false
val job = GlobalScope.launch { result = isInternetAvailable() }
while(job.isActive){}
return result*/
} }
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1) val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)

View File

@ -73,7 +73,8 @@ interface SpotiFlyerList {
data class State( data class State(
val queryResult: PlatformQueryResult? = null, val queryResult: PlatformQueryResult? = null,
val link: String = "", val link: String = "",
val trackList: List<TrackDetails> = emptyList() val trackList: List<TrackDetails> = emptyList(),
val errorOccurred: Exception? = null
) )
} }

View File

@ -58,6 +58,7 @@ internal class SpotiFlyerListStoreProvider(
data class ResultFetched(val result: PlatformQueryResult, val trackList: List<TrackDetails>) : Result() data class ResultFetched(val result: PlatformQueryResult, val trackList: List<TrackDetails>) : Result()
data class UpdateTrackList(val list: List<TrackDetails>) : Result() data class UpdateTrackList(val list: List<TrackDetails>) : Result()
data class UpdateTrackItem(val item: TrackDetails) : Result() data class UpdateTrackItem(val item: TrackDetails) : Result()
data class ErrorOccurred(val error: Exception) : Result()
} }
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() { private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
@ -74,10 +75,19 @@ internal class SpotiFlyerListStoreProvider(
override suspend fun executeIntent(intent: Intent, getState: () -> State) { override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) { when (intent) {
is Intent.SearchLink -> fetchQuery.query(link)?.let { result -> is Intent.SearchLink -> {
result.trackList = result.trackList.toMutableList() try {
dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))) val result = fetchQuery.query(link)
executeIntent(Intent.RefreshTracksStatuses, getState) 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 -> { is Intent.StartDownloadAll -> {
@ -107,6 +117,7 @@ internal class SpotiFlyerListStoreProvider(
is Result.ResultFetched -> copy(queryResult = result.result, trackList = result.trackList, link = link) is Result.ResultFetched -> copy(queryResult = result.result, trackList = result.trackList, link = link)
is Result.UpdateTrackList -> copy(trackList = result.list) is Result.UpdateTrackList -> copy(trackList = result.list)
is Result.UpdateTrackItem -> updateTrackItem(result.item) is Result.UpdateTrackItem -> updateTrackItem(result.item)
is Result.ErrorOccurred -> copy(errorOccurred = result.error)
} }
private fun State.updateTrackItem(item: TrackDetails): State { private fun State.updateTrackItem(item: TrackDetails): State {