mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
Error Handling
This commit is contained in:
parent
a0797bd891
commit
318c673cea
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 {}
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user