mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-26 07:59:35 -05:00
lib: add method to cancel in-flight requests
This commit is contained in:
parent
47efd85fd6
commit
56ecb91657
@ -6,4 +6,5 @@ import im.molly.monero.RemoteNode;
|
||||
interface IRemoteNodeClient {
|
||||
// RemoteNode getRemoteNode();
|
||||
HttpResponse makeRequest(String method, String path, String header, in byte[] body);
|
||||
oneway void cancelAll();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ ScopedJvmGlobalRef<jclass> OwnedTxOut;
|
||||
jmethodID HttpResponse_getBody;
|
||||
jmethodID HttpResponse_getCode;
|
||||
jmethodID HttpResponse_getContentType;
|
||||
jmethodID IRemoteNodeClient_cancelAll;
|
||||
jmethodID IRemoteNodeClient_makeRequest;
|
||||
jmethodID Logger_logFromNative;
|
||||
jmethodID OwnedTxOut_ctor;
|
||||
@ -29,6 +30,8 @@ void initializeJniCache(JNIEnv* env) {
|
||||
.getMethodId(env, "getCode", "()I");
|
||||
HttpResponse_getContentType = httpResponse
|
||||
.getMethodId(env, "getContentType", "()Ljava/lang/String;");
|
||||
IRemoteNodeClient_cancelAll = iRemoteNodeClient
|
||||
.getMethodId(env, "cancelAll", "()V");
|
||||
IRemoteNodeClient_makeRequest = iRemoteNodeClient
|
||||
.getMethodId(env,
|
||||
"makeRequest",
|
||||
|
@ -13,6 +13,7 @@ extern ScopedJvmGlobalRef<jclass> OwnedTxOut;
|
||||
extern jmethodID HttpResponse_getBody;
|
||||
extern jmethodID HttpResponse_getCode;
|
||||
extern jmethodID HttpResponse_getContentType;
|
||||
extern jmethodID IRemoteNodeClient_cancelAll;
|
||||
extern jmethodID IRemoteNodeClient_makeRequest;
|
||||
extern jmethodID Logger_logFromNative;
|
||||
extern jmethodID OwnedTxOut_ctor;
|
||||
|
@ -24,12 +24,13 @@ static_assert(CRYPTONOTE_MAX_BLOCK_NUMBER == 500000000,
|
||||
Wallet::Wallet(
|
||||
JNIEnv* env,
|
||||
int network_id,
|
||||
std::unique_ptr<HttpClientFactory> http_client_factory,
|
||||
const JvmRef<jobject>& remote_node_client,
|
||||
const JvmRef<jobject>& callback)
|
||||
: m_wallet(static_cast<cryptonote::network_type>(network_id),
|
||||
0, /* kdf_rounds */
|
||||
true, /* unattended */
|
||||
std::move(http_client_factory)),
|
||||
std::make_unique<RemoteNodeClientFactory>(env, remote_node_client)),
|
||||
m_remote_node_client(env, remote_node_client),
|
||||
m_callback(env, callback),
|
||||
m_account_ready(false),
|
||||
m_blockchain_height(1),
|
||||
@ -212,11 +213,9 @@ Java_im_molly_monero_WalletNative_nativeCreate(
|
||||
jobject thiz,
|
||||
jint network_id,
|
||||
jobject p_remote_node_client) {
|
||||
auto wallet = new Wallet(
|
||||
env, network_id,
|
||||
std::make_unique<RemoteNodeClientFactory>(
|
||||
env, JvmParamRef<jobject>(p_remote_node_client)),
|
||||
JvmParamRef<jobject>(thiz));
|
||||
auto wallet = new Wallet(env, network_id,
|
||||
JvmParamRef<jobject>(p_remote_node_client),
|
||||
JvmParamRef<jobject>(thiz));
|
||||
return nativeToJvmPointer(wallet);
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ class Wallet : tools::i_wallet2_callback {
|
||||
|
||||
Wallet(JNIEnv* env,
|
||||
int network_id,
|
||||
std::unique_ptr<HttpClientFactory> http_client_factory,
|
||||
const JvmRef<jobject>& remote_node_client,
|
||||
const JvmRef<jobject>& callback);
|
||||
|
||||
void restoreAccount(const std::vector<char>& secret_scalar, uint64_t account_timestamp);
|
||||
|
@ -2,23 +2,23 @@ package im.molly.monero
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.*
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
internal class RemoteNodeClient(
|
||||
private val nodeSelector: RemoteNodeSelector,
|
||||
private val httpClient: OkHttpClient,
|
||||
private val scope: CoroutineScope,
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
ioDispatcher: CoroutineDispatcher,
|
||||
) : IRemoteNodeClient.Stub() {
|
||||
|
||||
private val logger = loggerFor<RemoteNodeClient>()
|
||||
|
||||
/** Disable connecting to the Monero network */
|
||||
private val requestsScope = CoroutineScope(ioDispatcher + SupervisorJob())
|
||||
|
||||
// /** Disable connecting to the Monero network */
|
||||
// var offline = false
|
||||
|
||||
private fun selectedNode() = nodeSelector.select()
|
||||
@ -47,6 +47,11 @@ internal class RemoteNodeClient(
|
||||
}
|
||||
}
|
||||
|
||||
@CalledByNative("http_client.cc")
|
||||
override fun cancelAll() {
|
||||
requestsScope.coroutineContext.cancelChildren()
|
||||
}
|
||||
|
||||
private fun execute(
|
||||
method: String?,
|
||||
uri: Uri,
|
||||
@ -56,10 +61,12 @@ internal class RemoteNodeClient(
|
||||
password: String?,
|
||||
): HttpResponse {
|
||||
logger.d("HTTP: $method $uri, header_len=${header?.length}, body_size=${body?.size}")
|
||||
|
||||
val headers = header?.parseHttpHeader()
|
||||
val contentType = headers?.get("Content-Type")?.let { value ->
|
||||
MediaType.get(value)
|
||||
}
|
||||
|
||||
val request = with(Request.Builder()) {
|
||||
when (method) {
|
||||
"GET" -> {}
|
||||
@ -71,16 +78,28 @@ internal class RemoteNodeClient(
|
||||
url(uri.toString())
|
||||
build()
|
||||
}
|
||||
val response = httpClient.newCall(request).execute()
|
||||
return if (response.isSuccessful) {
|
||||
|
||||
val response = runBlocking(requestsScope.coroutineContext) {
|
||||
val call = httpClient.newCall(request)
|
||||
try {
|
||||
call.await()
|
||||
} catch (ioe: IOException) {
|
||||
if (!call.isCanceled) {
|
||||
throw ioe
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return if (response == null) {
|
||||
HttpResponse(code = 499)
|
||||
} else if (response.isSuccessful) {
|
||||
val responseBody = requireNotNull(response.body())
|
||||
val pipe = ParcelFileDescriptor.createPipe()
|
||||
scope.launch(ioDispatcher) {
|
||||
pipe[1].use { readFd ->
|
||||
FileOutputStream(readFd.fileDescriptor).use { outputStream ->
|
||||
responseBody.use {
|
||||
it.byteStream().copyTo(outputStream)
|
||||
}
|
||||
requestsScope.launch {
|
||||
pipe[1].use { writeSide ->
|
||||
FileOutputStream(writeSide.fileDescriptor).use { outputStream ->
|
||||
responseBody.byteStream().copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,13 +109,28 @@ internal class RemoteNodeClient(
|
||||
body = pipe[0],
|
||||
)
|
||||
} else {
|
||||
HttpResponse(code = response.code()).also {
|
||||
response.close()
|
||||
}
|
||||
HttpResponse(code = response.code())
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseHttpHeader(): Headers =
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun Call.await() = suspendCancellableCoroutine { continuation ->
|
||||
enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
continuation.resume(response) {
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
})
|
||||
|
||||
continuation.invokeOnCancellation { cancel() }
|
||||
}
|
||||
|
||||
fun String.parseHttpHeader(): Headers =
|
||||
with(Headers.Builder()) {
|
||||
splitToSequence("\r\n")
|
||||
.filter { line -> line.isNotEmpty() }
|
||||
|
@ -39,13 +39,11 @@ class WalletClient private constructor(
|
||||
network: MoneroNetwork,
|
||||
nodeSelector: RemoteNodeSelector? = null,
|
||||
httpClient: OkHttpClient? = null,
|
||||
coroutineContext: CoroutineContext = Dispatchers.Default + SupervisorJob(),
|
||||
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
): WalletClient {
|
||||
val scope = CoroutineScope(coroutineContext)
|
||||
val remoteNodeClient = nodeSelector?.let {
|
||||
requireNotNull(httpClient)
|
||||
RemoteNodeClient(it, httpClient, scope, ioDispatcher)
|
||||
RemoteNodeClient(it, httpClient, ioDispatcher)
|
||||
}
|
||||
val (serviceConnection, service) = bindService(context)
|
||||
return WalletClient(context, service, serviceConnection, network, remoteNodeClient)
|
||||
|
Loading…
Reference in New Issue
Block a user