diff --git a/lib/android/src/main/aidl/im/molly/monero/IWallet.aidl b/lib/android/src/main/aidl/im/molly/monero/IWallet.aidl index aa21cae..f141d8d 100644 --- a/lib/android/src/main/aidl/im/molly/monero/IWallet.aidl +++ b/lib/android/src/main/aidl/im/molly/monero/IWallet.aidl @@ -9,7 +9,7 @@ interface IWallet { void removeBalanceListener(in IBalanceListener listener); oneway void save(in ParcelFileDescriptor destination); oneway void resumeRefresh(boolean skipCoinbaseOutputs, in IRefreshCallback callback); - void cancelRefresh(); - void setRefreshSince(long heightOrTimestamp); + oneway void cancelRefresh(); + oneway void setRefreshSince(long heightOrTimestamp); void close(); } diff --git a/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt b/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt index dadbb6e..4ae153b 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt @@ -2,6 +2,7 @@ package im.molly.monero import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.onFailure import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -12,6 +13,8 @@ class MoneroWallet internal constructor( val remoteNodeClient: RemoteNodeClient?, ) : IWallet by wallet, AutoCloseable { + private val logger = loggerFor() + val publicAddress: String = wallet.primaryAccountAddress /** @@ -23,11 +26,18 @@ class MoneroWallet internal constructor( override fun onBalanceChanged(txOuts: List?, checkedAtBlockHeight: Long) { lastKnownLedger = Ledger(publicAddress, txOuts!!, checkedAtBlockHeight) - trySendBlocking(lastKnownLedger) + sendLedger(lastKnownLedger) } override fun onRefresh(blockchainHeight: Long) { - trySendBlocking(lastKnownLedger.copy(checkedAtBlockHeight = blockchainHeight)) + sendLedger(lastKnownLedger.copy(checkedAtBlockHeight = blockchainHeight)) + } + + private fun sendLedger(ledger: Ledger) { + trySend(ledger) + .onFailure { + logger.e("Too many ledger updates, channel capacity exceeded") + } } } diff --git a/lib/android/src/main/kotlin/im/molly/monero/WalletNative.kt b/lib/android/src/main/kotlin/im/molly/monero/WalletNative.kt index bef7c4f..b14b1b7 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/WalletNative.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/WalletNative.kt @@ -93,10 +93,16 @@ class WalletNative private constructor( } } - override fun cancelRefresh() = nativeCancelRefresh(handle) + override fun cancelRefresh() { + scope.launch(ioDispatcher) { + nativeCancelRefresh(handle) + } + } override fun setRefreshSince(blockHeightOrTimestamp: Long) { - nativeSetRefreshSince(handle, blockHeightOrTimestamp) + scope.launch(ioDispatcher) { + nativeSetRefreshSince(handle, blockHeightOrTimestamp) + } } /** @@ -158,7 +164,12 @@ class WalletNative private constructor( private val pendingRequestLock = ReentrantLock() - @CalledByNative("wallet.cc") + /** + * Invoked by native code to make a cancellable remote call to a remote node. + * + * Caller must close [HttpResponse.body] upon completion of processing the response. + */ + @CalledByNative("http_client.cc") private fun callRemoteNode( method: String?, path: String?, @@ -166,19 +177,26 @@ class WalletNative private constructor( body: ByteArray?, ): HttpResponse? = runBlocking { pendingRequestLock.withLock { - pendingRequest = if (requestsAllowed) { - async { - remoteNodeClient?.request(HttpRequest(method, path, header, body)) - } - } else null - } - runCatching { - pendingRequest?.await() - }.onFailure { throwable -> - if (throwable !is CancellationException) { - throw throwable + if (!requestsAllowed) { + return@runBlocking null } - }.getOrNull() + pendingRequest = async { + remoteNodeClient?.request(HttpRequest(method, path, header, body)) + } + } + try { + runCatching { + pendingRequest?.await() + }.onFailure { throwable -> + if (throwable is CancellationException) { + return@onFailure + } + logger.e("Error waiting for HTTP response", throwable) + throw throwable + }.getOrNull() + } finally { + pendingRequest = null + } } override fun close() {