From 73ffde670857082b3d526145fac5ba694793594b Mon Sep 17 00:00:00 2001 From: DatLag Date: Mon, 2 Oct 2023 14:03:18 +0200 Subject: [PATCH] added KCEF initializer --- example/src/main/kotlin/Main.kt | 42 +++-- .../kotlin/dev/datlag/kcef/CefException.kt | 9 +- kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt | 176 ++++++++++++++++++ .../kotlin/dev/datlag/kcef/KCEFBuilder.kt | 5 +- 4 files changed, 209 insertions(+), 23 deletions(-) create mode 100644 kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt diff --git a/example/src/main/kotlin/Main.kt b/example/src/main/kotlin/Main.kt index 5e7548e8..2351c50c 100644 --- a/example/src/main/kotlin/Main.kt +++ b/example/src/main/kotlin/Main.kt @@ -4,38 +4,40 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel import androidx.compose.ui.window.singleWindowApplication -import dev.datlag.kcef.kcef +import dev.datlag.kcef.KCEF import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.cef.CefApp import org.cef.browser.CefRendering fun main() = singleWindowApplication { var initialized by remember { mutableStateOf(false) } - var download by remember { mutableStateOf(0F) } + var download by remember { mutableStateOf(-1) } - val cefApp by produceState(null) { - value = withContext(Dispatchers.IO) { - kcef { - addArgs("--no-sandbox") - progress { - onInitialized { - initialized = true + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + KCEF.init( + builder = { + addArgs("--no-sandbox") + progress { + onInitialized { + initialized = true + } + onDownloading { + download = it.toInt() + } } - onDownloading { - download = it + settings { + noSandbox = true } + release(true) } - settings { - noSandbox = true - } - release(true) - }.build() + ) } } if (initialized) { - val client = cefApp!!.createClient() + // CEF is definitely initialized here, so we can use the blocking method without produceState + val client = KCEF.newClientBlocking() val browser = client.createBrowser( "https://github.com/DATL4G/KCEF", CefRendering.DEFAULT, @@ -49,7 +51,7 @@ fun main() = singleWindowApplication { modifier = Modifier.fillMaxSize() ) } else { - if (download > 0F) { + if (download > -1) { Text("Downloading: $download%") } else { Text("Initializing please wait...") @@ -58,7 +60,7 @@ fun main() = singleWindowApplication { DisposableEffect(Unit) { onDispose { - cefApp?.dispose() + KCEF.disposeBlocking() } } } \ No newline at end of file diff --git a/kcef/src/main/kotlin/dev/datlag/kcef/CefException.kt b/kcef/src/main/kotlin/dev/datlag/kcef/CefException.kt index 572eea23..47c8371e 100644 --- a/kcef/src/main/kotlin/dev/datlag/kcef/CefException.kt +++ b/kcef/src/main/kotlin/dev/datlag/kcef/CefException.kt @@ -1,7 +1,6 @@ package dev.datlag.kcef sealed class CefException(override val message: String) : Exception(message) { - data object Initialization : CefException("Error while initializing JCef") data object Startup : CefException("JCef did not initialize correctly!") data class UnsupportedPlatform( val os: String?, @@ -21,4 +20,12 @@ sealed class CefException(override val message: String) : Exception(message) { data object Download : CefException("Could not download jcef package") data object BadArchive : CefException("The provided archive contains a bad (malicious) file") + + data object NotInitialized : CefException("Cef was not initialized.") + + data object Disposed : CefException("Cef is disposed.") + + data object ApplicationRestartRequired : CefException("Application needs to restart.") + + data class Error(val exception: Throwable?) : CefException("Got error: ${exception?.message}") } \ No newline at end of file diff --git a/kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt b/kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt new file mode 100644 index 00000000..6a1f0d6a --- /dev/null +++ b/kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt @@ -0,0 +1,176 @@ +package dev.datlag.kcef + +import dev.datlag.kcef.common.existsSafely +import dev.datlag.kcef.common.suspendCatching +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.cef.CefApp +import org.cef.CefClient +import java.io.File +import kotlin.properties.Delegates + +data object KCEF { + + private val state: MutableStateFlow = MutableStateFlow(State.New) + private var cefApp by Delegates.notNull() + + @JvmOverloads + suspend fun init( + builder: KCEFBuilder.() -> Unit, + onError: (Throwable?) -> Unit = { }, + onRestartRequired: () -> Unit = { } + ) = init( + builder = KCEFBuilder().apply(builder), + onError = onError, + onRestartRequired = onRestartRequired + ) + + @JvmOverloads + fun initBlocking( + builder: KCEFBuilder.() -> Unit, + onError: (Throwable?) -> Unit = { }, + onRestartRequired: () -> Unit = { } + ) = runBlocking { + init(builder, onError, onRestartRequired) + } + + @JvmOverloads + suspend fun init( + builder: KCEFBuilder, + onError: (Throwable?) -> Unit = { }, + onRestartRequired: () -> Unit = { } + ) { + val currentBuilder = when (state.value) { + State.Disposed -> throw CefException.Disposed + State.Initializing, State.Initialized -> null + State.New, is State.Error -> { + state.emit(State.Initializing) + builder + } + } ?: return + + val installOk = File(builder.installDir, "install.lock").existsSafely() + + if (installOk) { + val result = suspendCatching { + currentBuilder.build() + } + setInitResult(result) + result.exceptionOrNull()?.let(onError) + } else { + val installResult = suspendCatching { + builder.install() + } + installResult.exceptionOrNull()?.let { + setInitResult(Result.failure(it)) + onError(it) + } + + val result = suspendCatching { + builder.build() + } + + setInitResult(result) + if (result.isFailure) { + result.exceptionOrNull()?.let(onError) + setInitResult(Result.failure(CefException.ApplicationRestartRequired)) + onRestartRequired.invoke() + } + } + } + + @JvmOverloads + fun initBlocking( + builder: KCEFBuilder, + onError: (Throwable?) -> Unit = { }, + onRestartRequired: () -> Unit = { } + ) = runBlocking { + init(builder, onError, onRestartRequired) + } + + suspend fun newClient(): CefClient { + return when (state.value) { + State.New -> throw CefException.NotInitialized + State.Disposed -> throw CefException.Disposed + is State.Error -> throw CefException.Error((state.value as? State.Error)?.exception) + State.Initialized -> cefApp.createClient() + State.Initializing -> { + state.first { it != State.Initializing } + + return newClient() + } + } + } + + fun newClientBlocking(): CefClient = runBlocking { + newClient() + } + + @JvmOverloads + suspend fun newClientOrNull(onError: (Throwable?) -> Unit = { }): CefClient? { + return when (state.value) { + State.New -> { + onError(CefException.NotInitialized) + null + } + State.Disposed -> { + onError(CefException.Disposed) + null + } + is State.Error -> { + onError(CefException.Error((state.value as? State.Error)?.exception)) + null + } + State.Initialized -> cefApp.createClient() + State.Initializing -> { + state.first { it != State.Initializing } + + return newClientOrNull(onError) + } + } + } + + @JvmOverloads + fun newClientOrNullBlocking(onError: (Throwable?) -> Unit = { }): CefClient? = runBlocking { + newClientOrNull(onError) + } + + suspend fun dispose() { + when (state.value) { + State.New, State.Disposed, is State.Error -> return + State.Initializing -> { + state.first { it != State.Initializing } + + return dispose() + } + State.Initialized -> { + state.emit(State.Disposed) + cefApp.dispose() + } + } + } + + fun disposeBlocking() = runBlocking { + dispose() + } + + private fun setInitResult(result: Result): Boolean { + val nextState = if (result.isSuccess) { + cefApp = result.getOrThrow() + State.Initialized + } else { + State.Error(result.exceptionOrNull()) + } + + return state.compareAndSet(State.Initializing, nextState) + } + + private sealed class State { + data object New : State() + data object Initializing : State() + data object Initialized : State() + data class Error(val exception: Throwable?) : State() + data object Disposed : State() + } +} \ No newline at end of file diff --git a/kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt b/kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt index 53fbc60e..b54623a5 100644 --- a/kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt +++ b/kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt @@ -16,7 +16,7 @@ import java.io.File class KCEFBuilder { - private var installDir: File = File("jcef-bundle") + internal var installDir: File = File("jcef-bundle") private var progress: InitProgress = InitProgress.Builder().build() private var settings: Settings = scopeCatching { @@ -435,7 +435,8 @@ class KCEFBuilder { uncaughtExceptionStackSize = settings.uncaught_exception_stack_size, userAgent = settings.user_agent, userAgentProduct = settings.user_agent_product, - windowlessRenderingEnabled = settings.windowless_rendering_enabled + windowlessRenderingEnabled = settings.windowless_rendering_enabled, + noSandbox = settings.no_sandbox ) } }