lib: initial support for InProcess provider

This commit is contained in:
Oscar Mira 2023-05-29 01:51:16 +02:00
parent f0c425dca9
commit 6811bdf47a
7 changed files with 58 additions and 41 deletions

View File

@ -8,9 +8,9 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
class MoneroSdkClient(private val context: Context) { class MoneroSdkClient(private val context: Context) {
@ -86,7 +86,7 @@ class MoneroSdkClient(private val context: Context) {
throw IOException("Cannot create wallet data directory: ${walletDataDir.path}") throw IOException("Cannot create wallet data directory: ${walletDataDir.path}")
} }
override suspend fun write(writer: (FileOutputStream) -> Unit) { override suspend fun write(writer: (OutputStream) -> Unit) {
val output = file.startWrite() val output = file.startWrite()
try { try {
writer(output) writer(output)
@ -97,7 +97,7 @@ class MoneroSdkClient(private val context: Context) {
} }
} }
override suspend fun read(): FileInputStream { override suspend fun read(): InputStream {
return file.openRead() return file.openRead()
} }
} }

View File

@ -2,5 +2,5 @@ package im.molly.monero;
interface IStorageAdapter { interface IStorageAdapter {
boolean writeAsync(in ParcelFileDescriptor pfd); boolean writeAsync(in ParcelFileDescriptor pfd);
void readAsync(in ParcelFileDescriptor pfd); oneway void readAsync(in ParcelFileDescriptor pfd);
} }

View File

@ -0,0 +1,10 @@
package im.molly.monero
import android.os.IInterface
/**
* Returns whether this interface is in a remote process.
*/
fun IInterface.isRemote(): Boolean {
return asBinder() !== this
}

View File

@ -17,30 +17,28 @@ internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter
override fun writeAsync(pfd: ParcelFileDescriptor?): Boolean { override fun writeAsync(pfd: ParcelFileDescriptor?): Boolean {
requireNotNull(pfd) requireNotNull(pfd)
val localDataStore = dataStore val localDataStore = dataStore
if (localDataStore == null) { return if (localDataStore != null) {
pfd.close() val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd)
return false ioStorageScope.launch {
} mutex.withLock {
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd) localDataStore.write { output ->
ioStorageScope.launch { inputStream.copyTo(output)
mutex.withLock { }
localDataStore.write { output ->
inputStream.copyTo(output)
} }
} }.invokeOnCompletion { inputStream.close() }
}.invokeOnCompletion { inputStream.close() } true
return true } else {
pfd.close()
false
}
} }
override fun readAsync(pfd: ParcelFileDescriptor?) { override fun readAsync(pfd: ParcelFileDescriptor?) {
requireNotNull(pfd) requireNotNull(pfd)
val localDataStore = dataStore
if (localDataStore == null) {
pfd.close()
throw IllegalArgumentException("WalletDataStore cannot be null")
}
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd) val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd)
ioStorageScope.launch { ioStorageScope.launch {
val localDataStore =
dataStore ?: throw IllegalArgumentException("WalletDataStore cannot be null")
mutex.withLock { mutex.withLock {
localDataStore.read().use { input -> localDataStore.read().use { input ->
input.copyTo(outputStream) input.copyTo(outputStream)

View File

@ -1,9 +1,9 @@
package im.molly.monero package im.molly.monero
import java.io.FileInputStream import java.io.InputStream
import java.io.FileOutputStream import java.io.OutputStream
interface WalletDataStore { interface WalletDataStore {
suspend fun write(writer: (FileOutputStream) -> Unit) suspend fun write(writer: (OutputStream) -> Unit)
suspend fun read(): FileInputStream suspend fun read(): InputStream
} }

View File

@ -11,7 +11,7 @@ import kotlin.coroutines.CoroutineContext
class WalletNative private constructor( class WalletNative private constructor(
networkId: Int, networkId: Int,
private val storageAdapter: IStorageAdapter?, private val storageAdapter: IStorageAdapter,
private val remoteNodeClient: IRemoteNodeClient?, private val remoteNodeClient: IRemoteNodeClient?,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher, private val ioDispatcher: CoroutineDispatcher,
@ -21,7 +21,7 @@ class WalletNative private constructor(
// TODO: Find better name because this is a local synchronization wallet, not a full node wallet // TODO: Find better name because this is a local synchronization wallet, not a full node wallet
suspend fun fullNode( suspend fun fullNode(
networkId: Int, networkId: Int,
storageAdapter: IStorageAdapter? = null, storageAdapter: IStorageAdapter,
remoteNodeClient: IRemoteNodeClient? = null, remoteNodeClient: IRemoteNodeClient? = null,
secretSpendKey: SecretKey? = null, secretSpendKey: SecretKey? = null,
restorePoint: Long? = null, restorePoint: Long? = null,
@ -59,13 +59,17 @@ class WalletNative private constructor(
private val handle: Long = nativeCreate(networkId) private val handle: Long = nativeCreate(networkId)
private suspend fun tryWriteState(): Boolean { private suspend fun tryWriteState(): Boolean {
requireNotNull(storageAdapter)
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
val pipe = ParcelFileDescriptor.createReliablePipe() val pipe = ParcelFileDescriptor.createPipe()
pipe[1].use { writeSide -> val readFd = pipe[0]
val storageIsReady = storageAdapter.writeAsync(pipe[0]) val writeFd = pipe[1]
val storageIsReady = storageAdapter.writeAsync(readFd)
if (storageAdapter.isRemote()) {
readFd.close()
}
writeFd.use {
if (storageIsReady) { if (storageIsReady) {
val result = nativeSave(handle, writeSide.fd) val result = nativeSave(handle, it.fd)
if (!result) { if (!result) {
logger.e("Wallet data serialization failed") logger.e("Wallet data serialization failed")
} }
@ -79,15 +83,18 @@ class WalletNative private constructor(
} }
private suspend fun readState() { private suspend fun readState() {
requireNotNull(storageAdapter) withContext(ioDispatcher) {
return withContext(ioDispatcher) { val pipe = ParcelFileDescriptor.createPipe()
val pipe = ParcelFileDescriptor.createReliablePipe() val readFd = pipe[0]
pipe[0].use { readSide -> val writeFd = pipe[1]
pipe[1].use { writeSide -> storageAdapter.readAsync(writeFd)
storageAdapter.readAsync(writeSide) if (storageAdapter.isRemote()) {
writeFd.close()
}
readFd.use {
if (!nativeLoad(handle, it.fd)) {
throw IllegalStateException("Wallet data deserialization failed")
} }
val result = nativeLoad(handle, readSide.fd)
check(result) { "Wallet data deserialization failed" }
} }
} }
} }

View File

@ -78,6 +78,7 @@ internal class WalletServiceImpl(
callback: IWalletServiceCallbacks?, callback: IWalletServiceCallbacks?,
) { ) {
requireNotNull(config) requireNotNull(config)
requireNotNull(storage)
serviceScope.launch { serviceScope.launch {
val wallet = WalletNative.fullNode( val wallet = WalletNative.fullNode(
networkId = config.networkId, networkId = config.networkId,
@ -97,6 +98,7 @@ internal class WalletServiceImpl(
restorePoint: Long? = null, restorePoint: Long? = null,
): IWallet { ): IWallet {
requireNotNull(config) requireNotNull(config)
requireNotNull(storage)
requireNotNull(secretSpendKey) requireNotNull(secretSpendKey)
return WalletNative.fullNode( return WalletNative.fullNode(
networkId = config.networkId, networkId = config.networkId,