diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/config/AppConfig.kt b/composeApp/src/commonMain/kotlin/com/clipevery/config/AppConfig.kt index 89da81ded..7c50c5e88 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/config/AppConfig.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/config/AppConfig.kt @@ -9,5 +9,6 @@ data class AppConfig( val appInstanceId: String = UUID.randomUUID().toString(), val language: String = Locale.getDefault().language, val isFollowSystemTheme: Boolean = true, - val isDarkTheme: Boolean = false + val isDarkTheme: Boolean = false, + val port: Int = 13129 ) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/DesktopClipeveryKoinApplication.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/DesktopClipeveryKoinApplication.kt index 8d02d51d4..5e237e85f 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/DesktopClipeveryKoinApplication.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/DesktopClipeveryKoinApplication.kt @@ -76,7 +76,7 @@ object Dependencies { // net component single { DesktopClipClient(get()) } single { DesktopClientHandlerManager(get(), get(), get(), get()) } - single { DesktopClipServer(get()).start() } + single { DesktopClipServer(get(), get()).start() } single> { lazy { get() } } single { DesktopClipBonjourService(get(), get()).registerService() } single { DesktopDeviceRefresher(get()) } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/net/DesktopClipServer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/net/DesktopClipServer.kt index 56e55bf38..73d268afb 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/net/DesktopClipServer.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/net/DesktopClipServer.kt @@ -1,5 +1,6 @@ package com.clipevery.net +import com.clipevery.config.ConfigManager import com.clipevery.exception.StandardErrorCode import com.clipevery.net.exception.signalExceptionHandler import com.clipevery.net.plugin.SignalDecryption @@ -27,49 +28,62 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.serializersModuleOf import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.state.PreKeyBundle +import java.net.BindException -class DesktopClipServer(private val clientHandlerManager :ClientHandlerManager): ClipServer { +class DesktopClipServer(private val configManager: ConfigManager, + private val clientHandlerManager :ClientHandlerManager): ClipServer { private val logger = KotlinLogging.logger {} private var port = 0 - private var server: NettyApplicationEngine = embeddedServer(Netty, port = 0) { - install(ContentNegotiation) { - json(Json { - serializersModule = SerializersModule { - serializersModuleOf(PreKeyBundle::class, PreKeyBundleSerializer) - serializersModuleOf(IdentityKey::class, IdentityKeySerializer) + private var server: NettyApplicationEngine = createServer(port = configManager.config.port) + + private fun createServer(port: Int): NettyApplicationEngine { + return embeddedServer(Netty, port = port) { + install(ContentNegotiation) { + json(Json { + serializersModule = SerializersModule { + serializersModuleOf(PreKeyBundle::class, PreKeyBundleSerializer) + serializersModuleOf(IdentityKey::class, IdentityKeySerializer) + } + }) + } + install(StatusPages) { + exception(Exception::class) { call, cause -> + logger.error(cause) { "Unhandled exception" } + failResponse(call, StandardErrorCode.UNKNOWN_ERROR.toErrorCode()) } - }) - } - install(StatusPages) { - exception(Exception::class) { call, cause -> - logger.error(cause) { "Unhandled exception" } - failResponse(call, StandardErrorCode.UNKNOWN_ERROR.toErrorCode()) + signalExceptionHandler() } - signalExceptionHandler() - } - install(SignalDecryption) { + install(SignalDecryption) { - } - intercept(ApplicationCallPipeline.Setup) { - logger.info {"Received request: ${call.request.httpMethod.value} ${call.request.uri} ${call.request.contentType()}" } - } - routing { - syncRouting() + } + intercept(ApplicationCallPipeline.Setup) { + logger.info {"Received request: ${call.request.httpMethod.value} ${call.request.uri} ${call.request.contentType()}" } + } + routing { + syncRouting() + } } } override fun start(): ClipServer { clientHandlerManager.start() - server.start(wait = false) + try { + server.start(wait = false) + } catch (e: BindException) { + logger.warn { "Port ${configManager.config.port} is already in use" } + server = createServer(port = 0) + server.start(wait = false) + } port = runBlocking { server.resolvedConnectors().first().port } - if (port == 0) { - logger.error { "Failed to start server" } - } else { - logger.info { "Server started at port $port" } + if (port != configManager.config.port) { + configManager.updateConfig { + it.copy(port = port) + } } + logger.info { "Server started at port $port" } return this }