mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
First steps for multiplatform API (#1003)
* First steps for multiplatform api * Buildconfig testing * Fix publishing and classes.jar * Update build.gradle.kts
This commit is contained in:
parent
5db541d7cc
commit
6df3ef14f6
13 changed files with 185 additions and 41 deletions
68
library/build.gradle.kts
Normal file
68
library/build.gradle.kts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import com.codingfeline.buildkonfig.compiler.FieldSpec
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("maven-publish")
|
||||
id("com.android.library")
|
||||
id("com.codingfeline.buildkonfig")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
version = "1.0.0"
|
||||
androidTarget()
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
|
||||
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
|
||||
Level 25 or Less. */
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
buildkonfig {
|
||||
packageName = "com.lagradost.api"
|
||||
exposeObjectWithName = "BuildConfig"
|
||||
|
||||
defaultConfigs {
|
||||
val isDebug = kotlin.runCatching { extra.get("isDebug") }.getOrNull() == true
|
||||
buildConfigField(FieldSpec.Type.BOOLEAN, "DEBUG", isDebug.toString())
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
targetSdk = 33
|
||||
}
|
||||
|
||||
// If this is the same com.lagradost.cloudstream3.R stops working
|
||||
namespace = "com.lagradost.api"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
publishing {
|
||||
publications {
|
||||
withType<MavenPublication> {
|
||||
groupId = "com.lagradost.api"
|
||||
}
|
||||
}
|
||||
}
|
2
library/src/androidMain/AndroidManifest.xml
Normal file
2
library/src/androidMain/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
21
library/src/androidMain/kotlin/com/lagradost/api/Log.kt
Normal file
21
library/src/androidMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,21 @@
|
|||
package com.lagradost.api
|
||||
|
||||
import android.util.Log
|
||||
|
||||
actual object Log {
|
||||
actual fun d(tag: String, message: String) {
|
||||
Log.d(tag, message)
|
||||
}
|
||||
|
||||
actual fun i(tag: String, message: String) {
|
||||
Log.i(tag, message)
|
||||
}
|
||||
|
||||
actual fun w(tag: String, message: String) {
|
||||
Log.w(tag, message)
|
||||
}
|
||||
|
||||
actual fun e(tag: String, message: String) {
|
||||
Log.e(tag, message)
|
||||
}
|
||||
}
|
8
library/src/commonMain/kotlin/com/lagradost/api/Log.kt
Normal file
8
library/src/commonMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,8 @@
|
|||
package com.lagradost.api
|
||||
|
||||
expect object Log {
|
||||
fun d(tag: String, message: String)
|
||||
fun i(tag: String, message: String)
|
||||
fun w(tag: String, message: String)
|
||||
fun e(tag: String, message: String)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
class ErrorLoadingException(message: String? = null) : Exception(message)
|
|
@ -0,0 +1,196 @@
|
|||
package com.lagradost.cloudstream3.mvvm
|
||||
|
||||
import com.lagradost.api.BuildConfig
|
||||
import com.lagradost.api.Log
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
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
|
||||
|
||||
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
|
||||
const val DEBUG_PRINT = "DEBUG PRINT"
|
||||
|
||||
class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message")
|
||||
|
||||
inline fun debugException(message: () -> String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw DebugException(message.invoke())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun debugPrint(tag: String = DEBUG_PRINT, message: () -> String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(tag, message.invoke())
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
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>()
|
||||
|
||||
data class Loading(val url: String? = null) : Resource<Nothing>()
|
||||
}
|
||||
|
||||
fun logError(throwable: Throwable) {
|
||||
Log.d("ApiError", "-------------------------------------------------------------------")
|
||||
Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage)
|
||||
Log.d("ApiError", "safeApiCall: " + throwable.message)
|
||||
throwable.printStackTrace()
|
||||
Log.d("ApiError", "-------------------------------------------------------------------")
|
||||
}
|
||||
|
||||
fun <T> normalSafeApiCall(apiCall: () -> T): T? {
|
||||
return try {
|
||||
apiCall.invoke()
|
||||
} catch (throwable: Throwable) {
|
||||
logError(throwable)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? {
|
||||
return try {
|
||||
apiCall.invoke()
|
||||
} catch (throwable: Throwable) {
|
||||
logError(throwable)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
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}"
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
||||
val stackTraceMsg = throwable.getStackTracePretty()
|
||||
return Resource.Failure(false, null, null, stackTraceMsg)
|
||||
}
|
||||
|
||||
fun CoroutineScope.launchSafe(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
): Job {
|
||||
val obj: suspend CoroutineScope.() -> Unit = {
|
||||
try {
|
||||
block()
|
||||
} catch (throwable: Throwable) {
|
||||
logError(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
return this.launch(context, start, obj)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> safeApiCall(
|
||||
apiCall: suspend () -> T,
|
||||
): Resource<T> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Resource.Success(apiCall.invoke())
|
||||
} catch (throwable: Throwable) {
|
||||
logError(throwable)
|
||||
throwAbleToResource(throwable)
|
||||
}
|
||||
}
|
||||
}
|
19
library/src/jvmMain/kotlin/com/lagradost/api/Log.kt
Normal file
19
library/src/jvmMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
package com.lagradost.api
|
||||
|
||||
actual object Log {
|
||||
actual fun d(tag: String, message: String) {
|
||||
println("DEBUG $tag: $message")
|
||||
}
|
||||
|
||||
actual fun i(tag: String, message: String) {
|
||||
println("INFO $tag: $message")
|
||||
}
|
||||
|
||||
actual fun w(tag: String, message: String) {
|
||||
println("WARNING $tag: $message")
|
||||
}
|
||||
|
||||
actual fun e(tag: String, message: String) {
|
||||
println("ERROR $tag: $message")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue