diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/app/AppInfo.kt b/composeApp/src/commonMain/kotlin/com/clipevery/app/AppInfo.kt index 5a01d77b8..4ac4bd143 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/app/AppInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/app/AppInfo.kt @@ -1,6 +1,8 @@ package com.clipevery.app +import com.clipevery.utils.JsonUtils import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString const val AppName: String = "Clipevery" @@ -9,4 +11,9 @@ data class AppInfo( val appInstanceId: String, val appVersion: String, val userName: String -) +) { + + override fun toString(): String { + return JsonUtils.JSON.encodeToString(this) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/dto/sync/SyncInfo.kt b/composeApp/src/commonMain/kotlin/com/clipevery/dto/sync/SyncInfo.kt index db31cf387..71b441f95 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/dto/sync/SyncInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/dto/sync/SyncInfo.kt @@ -2,7 +2,14 @@ package com.clipevery.dto.sync import com.clipevery.app.AppInfo import com.clipevery.endpoint.EndpointInfo +import com.clipevery.utils.JsonUtils import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString @Serializable -data class SyncInfo(val appInfo: AppInfo, val endpointInfo: EndpointInfo) +data class SyncInfo(val appInfo: AppInfo, val endpointInfo: EndpointInfo) { + + override fun toString(): String { + return JsonUtils.JSON.encodeToString(this) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/endpoint/EndpointInfo.kt b/composeApp/src/commonMain/kotlin/com/clipevery/endpoint/EndpointInfo.kt index 3141c81d9..ed5c73c3a 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/endpoint/EndpointInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/endpoint/EndpointInfo.kt @@ -2,18 +2,17 @@ package com.clipevery.endpoint import com.clipevery.dao.sync.HostInfo import com.clipevery.platform.Platform +import com.clipevery.utils.JsonUtils import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString @Serializable data class EndpointInfo(val deviceId: String, val deviceName: String, val platform: Platform, val hostInfoList: List, - val port: Int) - -data class ExplicitEndpointInfo(val deviceId: String, - val deviceName: String, - val platform: Platform, - val hostInfo: HostInfo, - val port: Int) - + val port: Int) { + override fun toString(): String { + return JsonUtils.JSON.encodeToString(this) + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt index ba6080368..71e83a6c8 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt @@ -11,11 +11,13 @@ import com.clipevery.dto.sync.RequestTrustSyncInfo import com.clipevery.dto.sync.SyncInfo import com.clipevery.exception.StandardErrorCode import com.clipevery.net.CheckAction +import com.clipevery.net.ClientHandlerManager import com.clipevery.net.DeviceRefresher import com.clipevery.utils.encodePreKeyBundle import com.clipevery.utils.failResponse import com.clipevery.utils.getAppInstanceId import com.clipevery.utils.successResponse +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.server.application.call import io.ktor.server.request.receive import io.ktor.server.routing.Routing @@ -34,6 +36,8 @@ import java.util.Objects fun Routing.syncRouting() { + val logger = KotlinLogging.logger {} + val koinApplication = Dependencies.koinApplication val signalDao = koinApplication.koin.get() @@ -44,6 +48,8 @@ fun Routing.syncRouting() { val deviceRefresher = koinApplication.koin.get() + val clientHandlerManager = koinApplication.koin.get() + post("/sync/syncInfos") { getAppInstanceId(call).let { appInstanceId -> val signalProtocolAddress = SignalProtocolAddress(appInstanceId, 1) @@ -53,6 +59,10 @@ fun Routing.syncRouting() { val identityKeys = requestTrustSyncInfos.map { ClipIdentityKey(it.syncInfo.appInfo.appInstanceId, it.identityKey.serialize()) } syncRuntimeInfoDao.inertOrUpdate(syncInfos) signalDao.saveIdentities(identityKeys) + for (syncInfo in syncInfos) { + logger.debug { "syncInfo = $syncInfo" } + clientHandlerManager.addHandler(syncInfo.appInfo.appInstanceId) + } deviceRefresher.refresh(CheckAction.CheckNonConnected) successResponse(call) } ?: failResponse(call, StandardErrorCode.SIGNAL_UNTRUSTED_IDENTITY.toErrorCode(), "not trust $appInstanceId") @@ -68,7 +78,7 @@ fun Routing.syncRouting() { val signalProtocolAddress = SignalProtocolAddress(appInstanceId, 1) - signalProtocolStore.getIdentity(signalProtocolAddress)?.let { + signalProtocolStore.getIdentity(signalProtocolAddress) ?: run { failResponse(call, StandardErrorCode.SIGNAL_UNTRUSTED_IDENTITY.toErrorCode(), "not trust $appInstanceId") return@get } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TelnetUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TelnetUtils.kt index 6a7552680..4d7d82a4c 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TelnetUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TelnetUtils.kt @@ -3,38 +3,51 @@ package com.clipevery.utils import com.clipevery.dao.sync.HostInfo import com.clipevery.net.ClipClient import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.selects.select +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withTimeout class TelnetUtils(private val clipClient: ClipClient) { private val logger = KotlinLogging.logger {} suspend fun switchHost(hostInfoList: List, port: Int, timeout: Long = 500L): HostInfo? { - if (hostInfoList.isEmpty()) { - return null - } - val deferredArray = hostInfoList.map { hostInfo -> - CoroutineScope(ioDispatcher).async { - if (telnet(hostInfo, port, timeout)) hostInfo else null - } - } + if (hostInfoList.isEmpty()) return null + + val result = CompletableDeferred() + val mutex = Mutex() + val scope = CoroutineScope(Dispatchers.IO) - var result: HostInfo? = null - select { - deferredArray.forEach { deferred -> - deferred.onAwait { hostInfo -> - if (hostInfo != null) { - result = hostInfo - deferredArray.forEach { it.cancel() } + hostInfoList.forEach { hostInfo -> + scope.launch { + try { + if (telnet(hostInfo, port, timeout)) { + mutex.withLock { + if (!result.isCompleted) { + result.complete(hostInfo) + } + } } - } + } catch (ignore: Exception) { } } } - return result + + return try { + withTimeout(timeout) { result.await() } + } catch (e: TimeoutCancellationException) { + null + } finally { + scope.cancel() + } } + private suspend fun telnet(hostInfo: HostInfo, port: Int, timeout: Long): Boolean { return try { val httpResponse = clipClient.get(timeout = timeout) { urlBuilder ->