From c04c1dee9f76ce75c56c35374a5ef3112f689132 Mon Sep 17 00:00:00 2001 From: osakila Date: Wed, 4 Sep 2024 10:53:54 +0900 Subject: [PATCH 1/2] Change exclusivity control of API calls --- .../com/ricoh360/thetaclient/ThetaApi.kt | 18 ++++++----- .../kotlin/com/ricoh360/thetaclient/Util.kt | 31 ++++++++++--------- .../repository/ThetaRepositoryTest.kt | 30 +++++++++++------- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt index 1e0d8a6d46..e26aec8bb6 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt @@ -16,11 +16,13 @@ import io.ktor.client.statement.* import io.ktor.serialization.kotlinx.json.* import io.ktor.utils.io.* import io.ktor.utils.io.core.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Semaphore import kotlinx.io.files.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json @@ -30,7 +32,7 @@ internal const val ALLOWED_CAPTURE_INTERVAL = 1000 /** * Http client using [Ktor](https://jp.ktor.work/clients/index.html) */ -@OptIn(ExperimentalSerializationApi::class) // explicitNulls +@OptIn(ExperimentalSerializationApi::class, ExperimentalCoroutinesApi::class) // explicitNulls internal object ThetaApi { val httpClient: HttpClient // for commands other than getLivePreview command get() = getHttpClient() @@ -41,7 +43,7 @@ internal object ThetaApi { val multipartPostClient: MultipartPostClient // just for updateFirmware protcol get() = getMultipartPostClient() - val requestSemaphore = Semaphore(1) + val requestScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) var lastSetTimeConsumingOptionTime: Long = 0 var currentOptions = Options() @@ -65,7 +67,7 @@ internal object ThetaApi { suspend fun callInfoApi( endpoint: String, ): InfoApiResponse { - return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { + return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { httpClient.get(getApiUrl(endpoint, InfoApi.PATH)).body() } } @@ -84,7 +86,7 @@ internal object ThetaApi { suspend fun callLicenseApi( endpoint: String, ): HttpResponse { - return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { + return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { httpClient.get(getApiUrl(endpoint, LicenseApi.PATH)) } } @@ -103,7 +105,7 @@ internal object ThetaApi { suspend fun callStateApi( endpoint: String, ): StateApiResponse { - return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { + return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { httpClient.post(getApiUrl(endpoint, StateApi.PATH)).body() } } @@ -126,7 +128,7 @@ internal object ThetaApi { endpoint: String, params: StatusApiParams, ): CommandApiResponse { - return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { + return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { val request = StatusApiRequest(name = params.name, id = params.id) val response = httpClient.post(getApiUrl(endpoint, StatusApi.PATH)) { headers { @@ -898,7 +900,7 @@ internal object ThetaApi { endpoint: String, body: T, ): HttpResponse { - return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { + return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { httpClient.post(getApiUrl(endpoint, CommandApi.PATH)) { headers { append("Content-Type", "application/json; charset=utf-8") diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt index 87a1f6277d..ab919fc788 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt @@ -1,7 +1,10 @@ package com.ricoh360.thetaclient import korlibs.crypto.md5 -import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout internal const val HEX_CHARACTERS = "0123456789abcdef" @@ -24,22 +27,22 @@ internal fun md5(data: String): String { } internal suspend fun syncExecutor( - semaphore: Semaphore, + scope: CoroutineScope, timeout: Long, run: suspend () -> T, ): T { - try { - withTimeout(timeout) { - semaphore.acquire() + val deferred = CompletableDeferred() + scope.launch { + runBlocking { + try { + withTimeout(timeout) { + val result = run() + deferred.complete(result) + } + } catch (e: Throwable) { + deferred.completeExceptionally(e) + } } - } catch (e: Throwable) { - println("timeout acquire") - throw e - } - try { - val result = run() - return result - } finally { - semaphore.release() } + return deferred.await() } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt index 7808f201db..29c521f9d9 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt @@ -7,7 +7,6 @@ import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import io.ktor.http.* import io.ktor.utils.io.* -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -15,7 +14,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class ThetaRepositoryTest { private val endpoint = "http://192.168.1.1:80/" @@ -793,15 +791,20 @@ class ThetaRepositoryTest { @Test fun callSingleRequestTimeoutTest() = runBlocking { - var counter = 0 - val jsonString = Resource("src/commonTest/resources/info/info_z1.json").readText() - MockApiClient.onRequest = { _ -> - counter += 1 - runBlocking { - delay(500) + val jsonInfo = Resource("src/commonTest/resources/info/info_z1.json").readText() + val jsonState = Resource("src/commonTest/resources/state/state_z1.json").readText() + MockApiClient.onRequest = { request -> + when (request.url.encodedPath) { + "/osc/info" -> ByteReadChannel(jsonInfo) + "/osc/state" -> { + runBlocking { + delay(500) + } + ByteReadChannel(jsonState) + } + + else -> throw Exception("Error") } - counter -= 1 - ByteReadChannel(jsonString) } val timeout = ThetaRepository.Timeout( @@ -812,10 +815,15 @@ class ThetaRepositoryTest { val apiJobsList = listOf( launch { thetaRepository.getThetaInfo() + try { + thetaRepository.getThetaInfo() + } catch (e: ThetaRepository.NotConnectedException) { + assertTrue(false) + } }, launch { try { - thetaRepository.getThetaInfo() + thetaRepository.getThetaState() assertTrue(false) } catch (e: ThetaRepository.NotConnectedException) { assertTrue((e.message?.indexOf("time", 0, true) ?: -1) >= 0, "timeout error") From 9867a2b6af36865a819b70abaae92144f1040a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:43:11 +0900 Subject: [PATCH 2/2] Update version to 1.10.2 --- demos/demo-android/app/build.gradle | 2 +- demos/demo-ios/Podfile | 2 +- demos/demo-react-native/package.json | 2 +- docs/tutorial-android.ja.md | 2 +- docs/tutorial-android.md | 2 +- flutter/android/build.gradle | 2 +- flutter/ios/theta_client_flutter.podspec | 4 ++-- flutter/pubspec.yaml | 2 +- kotlin-multiplatform/build.gradle.kts | 2 +- react-native/android/build.gradle | 2 +- react-native/package.json | 2 +- react-native/theta-client-react-native.podspec | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index 1d266bc7d5..12201de7d0 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -75,7 +75,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'io.coil-kt:coil-compose:2.2.2' implementation "io.ktor:ktor-client-cio:2.3.9" - implementation "com.ricoh360.thetaclient:theta-client:1.10.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.2" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" diff --git a/demos/demo-ios/Podfile b/demos/demo-ios/Podfile index d49d378596..10c4bccc97 100644 --- a/demos/demo-ios/Podfile +++ b/demos/demo-ios/Podfile @@ -7,5 +7,5 @@ target 'SdkSample' do use_frameworks! # Pods for SdkSample - pod 'THETAClient', '1.10.1' + pod 'THETAClient', '1.10.2' end diff --git a/demos/demo-react-native/package.json b/demos/demo-react-native/package.json index b706df7f41..183f11ed9f 100644 --- a/demos/demo-react-native/package.json +++ b/demos/demo-react-native/package.json @@ -13,7 +13,7 @@ "dependencies": { "@react-navigation/native": "^6.1.0", "@react-navigation/native-stack": "^6.9.5", - "theta-client-react-native": "1.10.1", + "theta-client-react-native": "1.10.2", "react": "18.2.0", "react-native": "0.71.14", "react-native-safe-area-context": "^4.4.1", diff --git a/docs/tutorial-android.ja.md b/docs/tutorial-android.ja.md index 7fdafad0a3..7d13ef42bd 100644 --- a/docs/tutorial-android.ja.md +++ b/docs/tutorial-android.ja.md @@ -4,7 +4,7 @@ - モジュールの`build.gradle`の`dependencies`に次を追加します。 ``` - implementation "com.ricoh360.thetaclient:theta-client:1.10.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.2" ``` - 本 SDK を使用したアプリケーションが動作するスマートフォンと THETA を無線 LAN 接続しておきます。 diff --git a/docs/tutorial-android.md b/docs/tutorial-android.md index 9207cfa435..aaa3427e1e 100644 --- a/docs/tutorial-android.md +++ b/docs/tutorial-android.md @@ -5,7 +5,7 @@ - Add following descriptions to the `dependencies` of your module's `build.gradle`. ``` - implementation "com.ricoh360.thetaclient:theta-client:1.10.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.2" ``` - Connect the wireless LAN between THETA and the smartphone that runs on the application using this SDK. diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle index cf4b96a93d..7370745a6c 100644 --- a/flutter/android/build.gradle +++ b/flutter/android/build.gradle @@ -53,5 +53,5 @@ dependencies { implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9") implementation("com.soywiz.korlibs.krypto:krypto:4.0.10") - implementation("com.ricoh360.thetaclient:theta-client:1.10.1") + implementation("com.ricoh360.thetaclient:theta-client:1.10.2") } diff --git a/flutter/ios/theta_client_flutter.podspec b/flutter/ios/theta_client_flutter.podspec index 3f18b48c16..eaa0c52c6c 100644 --- a/flutter/ios/theta_client_flutter.podspec +++ b/flutter/ios/theta_client_flutter.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'theta_client_flutter' - s.version = '1.10.1' + s.version = '1.10.2' s.summary = 'theta-client plugin project.' s.description = <<-DESC theta-client Flutter plugin project. @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.dependency 'Flutter' s.platform = :ios, '15.0' - s.dependency 'THETAClient', '1.10.1' + s.dependency 'THETAClient', '1.10.2' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 50f7b319c6..1a8a633ef2 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: theta_client_flutter description: THETA Client Flutter plugin project. -version: 1.10.1 +version: 1.10.2 homepage: environment: diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index dbd3ea580a..43dd3ec8dd 100644 --- a/kotlin-multiplatform/build.gradle.kts +++ b/kotlin-multiplatform/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.9.10") } -val thetaClientVersion = "1.10.1" +val thetaClientVersion = "1.10.2" group = "com.ricoh360.thetaclient" version = thetaClientVersion diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 87395227d7..6d2330eb92 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -135,7 +135,7 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" - implementation "com.ricoh360.thetaclient:theta-client:1.10.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.2" // From node_modules } diff --git a/react-native/package.json b/react-native/package.json index 7be7e549d8..79f0c1983d 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -1,6 +1,6 @@ { "name": "theta-client-react-native", - "version": "1.10.1", + "version": "1.10.2", "description": "This library provides a way to control RICOH THETA using.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native/theta-client-react-native.podspec b/react-native/theta-client-react-native.podspec index 0f3cb25055..b39ce33b11 100644 --- a/react-native/theta-client-react-native.podspec +++ b/react-native/theta-client-react-native.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" s.dependency "React-Core" - s.dependency "THETAClient", "1.10.1" + s.dependency "THETAClient", "1.10.2" # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then