2021-05-12 21:51:02 +00:00
|
|
|
package com.lagradost.cloudstream3.mvvm
|
|
|
|
|
|
|
|
import android.util.Log
|
|
|
|
import androidx.lifecycle.LifecycleOwner
|
|
|
|
import androidx.lifecycle.LiveData
|
2021-07-08 18:03:17 +00:00
|
|
|
import com.bumptech.glide.load.HttpException
|
2022-07-30 03:43:57 +00:00
|
|
|
import com.lagradost.cloudstream3.BuildConfig
|
2021-08-04 01:50:24 +00:00
|
|
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
2022-08-18 00:54:05 +00:00
|
|
|
import kotlinx.coroutines.*
|
2022-09-13 08:52:51 +00:00
|
|
|
import java.io.InterruptedIOException
|
2021-07-08 18:03:17 +00:00
|
|
|
import java.net.SocketTimeoutException
|
|
|
|
import java.net.UnknownHostException
|
2021-12-21 18:58:07 +00:00
|
|
|
import javax.net.ssl.SSLHandshakeException
|
2022-08-18 00:54:05 +00:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
|
|
|
import kotlin.coroutines.EmptyCoroutineContext
|
2021-05-12 21:51:02 +00:00
|
|
|
|
2022-07-30 03:43:57 +00:00
|
|
|
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
|
2022-09-23 12:20:53 +00:00
|
|
|
const val DEBUG_PRINT = "DEBUG PRINT"
|
2022-07-30 03:43:57 +00:00
|
|
|
|
|
|
|
class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message")
|
|
|
|
|
|
|
|
inline fun debugException(message: () -> String) {
|
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
throw DebugException(message.invoke())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 12:20:53 +00:00
|
|
|
inline fun debugPrint(tag: String = DEBUG_PRINT, message: () -> String) {
|
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
Log.d(tag, message.invoke())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-30 03:43:57 +00:00
|
|
|
inline fun debugWarning(message: () -> String) {
|
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
logError(DebugException(message.invoke()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline fun debugAssert(assert: () -> Boolean, message: () -> String) {
|
|
|
|
if (BuildConfig.DEBUG && assert.invoke()) {
|
|
|
|
throw DebugException(message.invoke())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
|
|
|
|
if (BuildConfig.DEBUG && assert.invoke()) {
|
|
|
|
logError(DebugException(message.invoke()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-15 20:40:10 +00:00
|
|
|
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
2021-08-29 18:42:44 +00:00
|
|
|
liveData.observe(this) { it?.let { t -> action(t) } }
|
2021-05-12 21:51:02 +00:00
|
|
|
}
|
|
|
|
|
2023-01-17 15:57:46 +00:00
|
|
|
fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
|
|
|
liveData.observe(this) { action(it) }
|
|
|
|
}
|
|
|
|
|
2021-05-12 21:51:02 +00:00
|
|
|
sealed class Resource<out T> {
|
|
|
|
data class Success<out T>(val value: T) : Resource<T>()
|
|
|
|
data class Failure(
|
|
|
|
val isNetworkError: Boolean,
|
|
|
|
val errorCode: Int?,
|
|
|
|
val errorResponse: Any?, //ResponseBody
|
|
|
|
val errorString: String,
|
|
|
|
) : Resource<Nothing>()
|
2021-08-04 01:50:24 +00:00
|
|
|
|
|
|
|
data class Loading(val url: String? = null) : Resource<Nothing>()
|
2021-05-12 21:51:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-17 16:20:05 +00:00
|
|
|
fun logError(throwable: Throwable) {
|
|
|
|
Log.d("ApiError", "-------------------------------------------------------------------")
|
|
|
|
Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage)
|
|
|
|
Log.d("ApiError", "safeApiCall: " + throwable.message)
|
|
|
|
throwable.printStackTrace()
|
|
|
|
Log.d("ApiError", "-------------------------------------------------------------------")
|
|
|
|
}
|
|
|
|
|
2021-08-04 01:50:24 +00:00
|
|
|
fun <T> normalSafeApiCall(apiCall: () -> T): T? {
|
2021-06-17 16:20:05 +00:00
|
|
|
return try {
|
|
|
|
apiCall.invoke()
|
|
|
|
} catch (throwable: Throwable) {
|
|
|
|
logError(throwable)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-29 21:25:12 +00:00
|
|
|
suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? {
|
|
|
|
return try {
|
|
|
|
apiCall.invoke()
|
|
|
|
} catch (throwable: Throwable) {
|
|
|
|
logError(throwable)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-15 20:40:10 +00:00
|
|
|
fun Throwable.getAllMessages(): String {
|
|
|
|
return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "")
|
|
|
|
}
|
|
|
|
|
|
|
|
fun Throwable.getStackTracePretty(showMessage: Boolean = true): String {
|
|
|
|
val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else ""
|
|
|
|
return prefix + this.stackTrace.joinToString(
|
|
|
|
separator = "\n"
|
|
|
|
) {
|
|
|
|
"${it.fileName} ${it.lineNumber}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 18:58:07 +00:00
|
|
|
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
2023-02-15 20:40:10 +00:00
|
|
|
val stackTraceMsg = throwable.getStackTracePretty()
|
2021-12-21 18:58:07 +00:00
|
|
|
return Resource.Failure(false, null, null, stackTraceMsg)
|
|
|
|
}
|
|
|
|
|
2022-08-18 00:54:05 +00:00
|
|
|
fun CoroutineScope.launchSafe(
|
|
|
|
context: CoroutineContext = EmptyCoroutineContext,
|
|
|
|
start: CoroutineStart = CoroutineStart.DEFAULT,
|
|
|
|
block: suspend CoroutineScope.() -> Unit
|
|
|
|
): Job {
|
|
|
|
val obj: suspend CoroutineScope.() -> Unit = {
|
|
|
|
try {
|
|
|
|
block()
|
2022-08-25 01:59:20 +00:00
|
|
|
} catch (throwable: Throwable) {
|
|
|
|
logError(throwable)
|
2022-08-18 00:54:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.launch(context, start, obj)
|
|
|
|
}
|
|
|
|
|
2023-07-19 16:47:22 +00:00
|
|
|
fun<T> throwAbleToResource(
|
|
|
|
throwable: Throwable
|
|
|
|
): Resource<T> {
|
|
|
|
return when (throwable) {
|
|
|
|
is NullPointerException -> {
|
|
|
|
for (line in throwable.stackTrace) {
|
|
|
|
if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) {
|
|
|
|
return Resource.Failure(
|
|
|
|
false,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
"NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
safeFail(throwable)
|
|
|
|
}
|
|
|
|
is SocketTimeoutException, is InterruptedIOException -> {
|
|
|
|
Resource.Failure(
|
|
|
|
true,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
"Connection Timeout\nPlease try again later."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
is HttpException -> {
|
|
|
|
Resource.Failure(
|
|
|
|
false,
|
|
|
|
throwable.statusCode,
|
|
|
|
null,
|
|
|
|
throwable.message ?: "HttpException"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
is UnknownHostException -> {
|
|
|
|
Resource.Failure(true, null, null, "Cannot connect to server, try again later.\n${throwable.message}")
|
|
|
|
}
|
|
|
|
is ErrorLoadingException -> {
|
|
|
|
Resource.Failure(
|
|
|
|
true,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
throwable.message ?: "Error loading, try again later."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
is NotImplementedError -> {
|
|
|
|
Resource.Failure(false, null, null, "This operation is not implemented.")
|
|
|
|
}
|
|
|
|
is SSLHandshakeException -> {
|
|
|
|
Resource.Failure(
|
|
|
|
true,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
(throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
is CancellationException -> {
|
|
|
|
throwable.cause?.let {
|
|
|
|
throwAbleToResource(it)
|
|
|
|
} ?: safeFail(throwable)
|
|
|
|
}
|
|
|
|
else -> safeFail(throwable)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-12 21:51:02 +00:00
|
|
|
suspend fun <T> safeApiCall(
|
|
|
|
apiCall: suspend () -> T,
|
|
|
|
): Resource<T> {
|
|
|
|
return withContext(Dispatchers.IO) {
|
|
|
|
try {
|
|
|
|
Resource.Success(apiCall.invoke())
|
|
|
|
} catch (throwable: Throwable) {
|
2021-06-17 16:20:05 +00:00
|
|
|
logError(throwable)
|
2023-07-19 16:47:22 +00:00
|
|
|
throwAbleToResource(throwable)
|
2021-05-12 21:51:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|