From 60dd5ba69b4859142a193e0383ece9d7850f9482 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 12:35:42 -0600 Subject: [PATCH 1/9] Coroutines KMP API --- .../cloudstream3/mvvm/ArchComponentExt.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index e13bcf5ec65..f3313c9dad6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -1,8 +1,11 @@ package com.lagradost.cloudstream3.mvvm +import androidx.annotation.AnyThread +import androidx.annotation.WorkerThread import com.lagradost.api.Log import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug +import com.lagradost.cloudstream3.utils.Coroutines.ioWork import kotlinx.coroutines.* import java.io.InterruptedIOException import java.net.SocketTimeoutException @@ -189,14 +192,6 @@ fun throwAbleToResource( "Connection Timeout\nPlease try again later." ) } -// is HttpException -> { -// Resource.Failure( -// false, -// throwable.statusCode, -// null, -// throwable.message ?: "HttpException" -// ) -// } is UnknownHostException -> { Resource.Failure( true, @@ -232,12 +227,13 @@ fun throwAbleToResource( } } +@AnyThread suspend fun safeApiCall( - apiCall: suspend () -> T, + @WorkerThread apiCall: suspend () -> T, ): Resource { - return withContext(Dispatchers.IO) { + return ioWork { try { - Resource.Success(apiCall.invoke()) + Resource.Success(apiCall()) } catch (throwable: Throwable) { logError(throwable) throwAbleToResource(throwable) From dca3a3a50e3c648cc1630fd1b52ecb3851e010d2 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 12:59:29 -0600 Subject: [PATCH 2/9] Update --- .../cloudstream3/mvvm/ArchComponentExt.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index f3313c9dad6..45afde9c619 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -73,6 +73,7 @@ sealed class Resource { fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") + // localizedMessage is JVM only - remove and just use message? Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage) Log.d("ApiError", "safeApiCall: " + throwable.message) throwable.printStackTrace() @@ -130,11 +131,14 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } fun Throwable.getAllMessages(): String { + // localizedMessage is JVM only - just use message? return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") } fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { + // localizedMessage is JVM only - just use message? val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" + // stackTrace is JVM only - maybe use stackTraceToString? return prefix + this.stackTrace.joinToString( separator = "\n" ) { @@ -167,6 +171,7 @@ fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { + // JVM only - maybe use NoSuchElementException? is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { Resource.Failure( false, @@ -175,6 +180,7 @@ fun throwAbleToResource( } is NullPointerException -> { + // stackTrace is JVM only - maybe use stackTraceToString? for (line in throwable.stackTrace) { if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) { return Resource.Failure( @@ -186,13 +192,13 @@ fun throwAbleToResource( safeFail(throwable) } - is SocketTimeoutException, is InterruptedIOException -> { + is SocketTimeoutException, is InterruptedIOException -> { // JVM only Resource.Failure( true, "Connection Timeout\nPlease try again later." ) } - is UnknownHostException -> { + is UnknownHostException -> { // JVM only Resource.Failure( true, "Cannot connect to server, try again later.\n${throwable.message}" @@ -210,7 +216,7 @@ fun throwAbleToResource( Resource.Failure(false, "This operation is not implemented.") } - is SSLHandshakeException -> { + is SSLHandshakeException -> { // JVM only Resource.Failure( true, (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." @@ -231,9 +237,9 @@ fun throwAbleToResource( suspend fun safeApiCall( @WorkerThread apiCall: suspend () -> T, ): Resource { - return ioWork { + return apiCall.ioWork { try { - Resource.Success(apiCall()) + Resource.Success(it()) } catch (throwable: Throwable) { logError(throwable) throwAbleToResource(throwable) From 59d30ff5266d5cfe5392921d4873f82478517b03 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:28:09 -0600 Subject: [PATCH 3/9] Update --- .../cloudstream3/mvvm/ArchComponentExt.kt | 71 ++++++------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 45afde9c619..181fb768548 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -7,13 +7,8 @@ import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug import com.lagradost.cloudstream3.utils.Coroutines.ioWork import kotlinx.coroutines.* -import java.io.InterruptedIOException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import javax.net.ssl.SSLHandshakeException import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -import kotlin.reflect.full.NoSuchPropertyException const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_PRINT = "DEBUG PRINT" @@ -62,7 +57,7 @@ sealed class Resource { companion object { fun fromResult(result: Result) : Resource { val value = result.getOrNull() - return if(value != null) { + return if (value != null) { Success(value) } else { throwAbleToResource(result.exceptionOrNull() ?: Exception("this should not be possible")) @@ -73,8 +68,6 @@ sealed class Resource { fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") - // localizedMessage is JVM only - remove and just use message? - Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage) Log.d("ApiError", "safeApiCall: " + throwable.message) throwable.printStackTrace() Log.d("ApiError", "-------------------------------------------------------------------") @@ -131,19 +124,18 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } fun Throwable.getAllMessages(): String { - // localizedMessage is JVM only - just use message? - return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") + return (this.message ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") } fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - // localizedMessage is JVM only - just use message? - val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" - // stackTrace is JVM only - maybe use stackTraceToString? - return prefix + this.stackTrace.joinToString( - separator = "\n" - ) { - "${it.fileName} ${it.lineNumber}" - } + val prefix = if (showMessage) this.message?.let { "\n$it" } ?: "" else "" + return prefix + this.stackTraceToString() + .lines() + .mapNotNull { line -> + val trimmed = line.trim() + if (trimmed.startsWith("at ")) trimmed.removePrefix("at ") else null + } + .joinToString("\n") } fun safeFail(throwable: Throwable): Resource { @@ -167,12 +159,13 @@ fun CoroutineScope.launchSafe( return this.launch(context, start, obj) } +expect fun platformThrowAbleToResource(throwable: Throwable): Resource + fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { - // JVM only - maybe use NoSuchElementException? - is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + is NoSuchElementException -> { Resource.Failure( false, "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? @@ -180,31 +173,18 @@ fun throwAbleToResource( } is NullPointerException -> { - // stackTrace is JVM only - maybe use stackTraceToString? - for (line in throwable.stackTrace) { - if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) { - return Resource.Failure( - false, - "NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection" - ) - } + val traceLine = throwable.stackTraceToString() + .lines() + .firstOrNull { it.contains("provider.kt", ignoreCase = true) } + if (traceLine != null) { + return Resource.Failure( + false, + "NullPointerException at $traceLine\nSite might have updated or added Cloudflare/DDOS protection" + ) } safeFail(throwable) } - is SocketTimeoutException, is InterruptedIOException -> { // JVM only - Resource.Failure( - true, - "Connection Timeout\nPlease try again later." - ) - } - is UnknownHostException -> { // JVM only - Resource.Failure( - true, - "Cannot connect to server, try again later.\n${throwable.message}" - ) - } - is ErrorLoadingException -> { Resource.Failure( true, @@ -216,20 +196,13 @@ fun throwAbleToResource( Resource.Failure(false, "This operation is not implemented.") } - is SSLHandshakeException -> { // JVM only - Resource.Failure( - true, - (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." - ) - } - is CancellationException -> { throwable.cause?.let { throwAbleToResource(it) } ?: safeFail(throwable) } - else -> safeFail(throwable) + else -> platformThrowAbleToResource(throwable) } } From 6a22b94f13c95b0352fd887a8926168e9d35e16c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:30:34 -0600 Subject: [PATCH 4/9] add --- .../mvvm/ArchComponentExt.android.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 library/src/androidMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.android.kt diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.android.kt new file mode 100644 index 00000000000..f2ff5e3acb0 --- /dev/null +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.android.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.mvvm + +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException +import kotlin.reflect.full.NoSuchPropertyException + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + return when (throwable) { + is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + Resource.Failure( + false, + "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? + ) + } + is SocketTimeoutException, is InterruptedIOException -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + is UnknownHostException -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + is SSLHandshakeException -> { + Resource.Failure( + true, + (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." + ) + } + else -> safeFail(throwable) + } +} From 7f81b64d00d29c9478249fa7e9e5cd901fa14b48 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:30:59 -0600 Subject: [PATCH 5/9] Add --- .../cloudstream3/mvvm/ArchComponentExt.jvm.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 library/src/jvmMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvm.kt diff --git a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvm.kt b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvm.kt new file mode 100644 index 00000000000..f2ff5e3acb0 --- /dev/null +++ b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvm.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.mvvm + +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException +import kotlin.reflect.full.NoSuchPropertyException + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + return when (throwable) { + is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + Resource.Failure( + false, + "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? + ) + } + is SocketTimeoutException, is InterruptedIOException -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + is UnknownHostException -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + is SSLHandshakeException -> { + Resource.Failure( + true, + (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." + ) + } + else -> safeFail(throwable) + } +} From be82764b3c9ce607e553206ea3e33930cbbdd5bf Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:34:15 -0600 Subject: [PATCH 6/9] Update --- .../com/lagradost/cloudstream3/utils/Coroutines.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index c525a1f36b0..a898b0831f1 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -10,6 +10,9 @@ import java.util.Collections.synchronizedList @AnyThread expect fun runOnMainThreadNative(@MainThread work: (() -> Unit)) + +expect val workerDispatcher: CoroutineDispatcher + object Coroutines { @AnyThread fun T.main(@MainThread work: suspend ((T) -> Unit)): Job { @@ -22,7 +25,7 @@ object Coroutines { @AnyThread fun T.ioSafe(@WorkerThread work: suspend (CoroutineScope.(T) -> Unit)): Job { val value = this - return CoroutineScope(Dispatchers.IO).launchSafe { + return CoroutineScope(workerDispatcher).launchSafe { work(value) } } @@ -30,7 +33,7 @@ object Coroutines { @AnyThread suspend fun V.ioWorkSafe(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T? { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { try { work(value) } catch (e: Exception) { @@ -43,7 +46,7 @@ object Coroutines { @AnyThread suspend fun V.ioWork(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { work(value) } } From 76060f5fdfd65497bcdd2e844df6e5dab310f675 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:35:57 -0600 Subject: [PATCH 7/9] Add --- .../com/lagradost/cloudstream3/utils/Coroutines.android.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt index 048e7fc0237..7de87eeb282 100644 --- a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.android.kt @@ -4,6 +4,8 @@ import android.os.Handler import android.os.Looper import androidx.annotation.AnyThread import androidx.annotation.MainThread +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers @AnyThread actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { @@ -12,3 +14,5 @@ actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { work() } } + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.IO From 4d05cbb8c6e1e17de71218a9b27013c90c01f6d9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:37:00 -0600 Subject: [PATCH 8/9] Add --- .../kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvm.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvm.kt b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvm.kt index 8fc9a8b0f01..5e8c09c847b 100644 --- a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvm.kt +++ b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvm.kt @@ -2,8 +2,12 @@ package com.lagradost.cloudstream3.utils import androidx.annotation.AnyThread import androidx.annotation.MainThread +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers @AnyThread actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { work.invoke() } + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.IO From b1ecd3e40531c95ff46b9be6f8c0dc75e7e52856 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 24 May 2026 13:46:36 -0600 Subject: [PATCH 9/9] - --- .../com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 181fb768548..aee776456fc 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -165,13 +165,6 @@ fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { - is NoSuchElementException -> { - Resource.Failure( - false, - "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? - ) - } - is NullPointerException -> { val traceLine = throwable.stackTraceToString() .lines()