From f41569513b47fc02e13f7dc09986677504bcc5fc Mon Sep 17 00:00:00 2001
From: Yiqun Zhang <guiyanakuang@gmail.com>
Date: Tue, 6 Feb 2024 18:19:16 +0800
Subject: [PATCH] :zap: Try to use fixed port

---
 .../kotlin/com/clipevery/config/AppConfig.kt  |  3 +-
 .../DesktopClipeveryKoinApplication.kt        |  2 +-
 .../com/clipevery/net/DesktopClipServer.kt    | 68 +++++++++++--------
 3 files changed, 44 insertions(+), 29 deletions(-)

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<ClipClient> { DesktopClipClient(get<AppInfo>()) }
             single<ClientHandlerManager> { DesktopClientHandlerManager(get(), get(), get(), get()) }
-            single<ClipServer> { DesktopClipServer(get<ClientHandlerManager>()).start() }
+            single<ClipServer> { DesktopClipServer(get<ConfigManager>(), get<ClientHandlerManager>()).start() }
             single<Lazy<ClipServer>> { lazy { get<ClipServer>() } }
             single<ClipBonjourService> { DesktopClipBonjourService(get(), get()).registerService() }
             single<DeviceRefresher> { DesktopDeviceRefresher(get<ClientHandlerManager>()) }
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
     }