summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt')
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt203
1 files changed, 68 insertions, 135 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index a2b48b6..4e179bb 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -17,171 +17,104 @@
package net.taler.wallet.backend
import android.app.Application
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.Messenger
-import android.util.Log
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
-import kotlinx.serialization.json.Json
-import net.taler.lib.android.CustomClassDiscriminator
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_SUBSCRIBE_NOTIFY
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.decodeFromJsonElement
+import net.taler.wallet.backend.TalerErrorCode.NONE
import org.json.JSONObject
-import java.lang.ref.WeakReference
-import java.util.LinkedList
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlin.reflect.full.companionObjectInstance
+import java.io.File
+private const val WALLET_DB = "talerwalletdb.sqlite3"
+
+@OptIn(DelicateCoroutinesApi::class)
class WalletBackendApi(
private val app: Application,
- private val notificationHandler: ((payload: JSONObject) -> Unit)
+ private val versionReceiver: VersionReceiver,
+ notificationReceiver: NotificationReceiver,
) {
- private var walletBackendMessenger: Messenger? = null
- private val queuedMessages = LinkedList<Message>()
- private val handlers = ConcurrentHashMap<Int, (isError: Boolean, message: JSONObject) -> Unit>()
- private var nextRequestID = AtomicInteger(0)
- private val incomingMessenger = Messenger(IncomingHandler(this))
-
- private val walletBackendConn = object : ServiceConnection {
- override fun onServiceDisconnected(p0: ComponentName?) {
- Log.w(TAG, "wallet backend service disconnected (crash?)")
- walletBackendMessenger = null
- }
- override fun onServiceConnected(componentName: ComponentName?, binder: IBinder?) {
- Log.i(TAG, "connected to wallet backend service")
- val bm = Messenger(binder)
- walletBackendMessenger = bm
- pumpQueue(bm)
- val msg = Message.obtain(null, MSG_SUBSCRIBE_NOTIFY)
- msg.replyTo = incomingMessenger
- bm.send(msg)
- }
- }
+ private val backendManager = BackendManager(notificationReceiver)
init {
- Intent(app, WalletBackendService::class.java).also { intent ->
- app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE)
+ GlobalScope.launch(Dispatchers.IO) {
+ backendManager.run()
+ sendInitMessage()
}
}
- private class IncomingHandler(strongApi: WalletBackendApi) : Handler() {
- private val weakApi = WeakReference(strongApi)
- override fun handleMessage(msg: Message) {
- val api = weakApi.get() ?: return
- when (msg.what) {
- MSG_REPLY -> {
- val requestID = msg.data.getInt("requestID", 0)
- val operation = msg.data.getString("operation", "??")
- Log.i(TAG, "got reply for operation $operation ($requestID)")
- val h = api.handlers.remove(requestID)
- if (h == null) {
- Log.e(TAG, "request ID not associated with a handler")
- return
- }
- val response = msg.data.getString("response")
- if (response == null) {
- Log.e(TAG, "response did not contain response payload")
- return
- }
- val isError = msg.data.getBoolean("isError")
- val json = JSONObject(response)
- h(isError, json)
- }
- MSG_NOTIFY -> {
- val payloadStr = msg.data.getString("payload")
- if (payloadStr == null) {
- Log.e(TAG, "Notification had no payload: $msg")
- } else {
- val payload = JSONObject(payloadStr)
- api.notificationHandler.invoke(payload)
- }
- }
- }
+ private suspend fun sendInitMessage() {
+ val db = if (File(app.filesDir, "talerwalletdb.sql").isFile) {
+ // can be removed after a reasonable migration period (2024-02-02)
+ "${app.filesDir}/talerwalletdb.sql"
+ } else {
+ "${app.filesDir}/${WALLET_DB}"
}
- }
-
- private fun pumpQueue(bm: Messenger) {
- while (true) {
- val msg = queuedMessages.pollFirst() ?: return
- bm.send(msg)
+ request("init", InitResponse.serializer()) {
+ put("persistentStoragePath", db)
+ put("logLevel", "INFO")
+ }.onSuccess { response ->
+ versionReceiver.onVersionReceived(response.versionInfo)
+ }.onError { error ->
+ // TODO expose this to the UI as it can happen when using an older DB version
+ error("Error on init message: $error")
}
}
- fun sendRequest(
- operation: String,
- args: JSONObject? = null,
- onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ -> }
- ) {
- val requestID = nextRequestID.incrementAndGet()
- Log.i(TAG, "sending request for operation $operation ($requestID)")
- val msg = Message.obtain(null, MSG_COMMAND)
- handlers[requestID] = onResponse
- msg.replyTo = incomingMessenger
- val data = msg.data
- data.putString("operation", operation)
- data.putInt("requestID", requestID)
- if (args != null) {
- data.putString("args", args.toString())
- }
- val bm = walletBackendMessenger
- if (bm != null) {
- bm.send(msg)
- } else {
- queuedMessages.add(msg)
- }
+ suspend fun sendRequest(operation: String, args: JSONObject? = null): ApiResponse {
+ return backendManager.send(operation, args)
}
suspend inline fun <reified T> request(
operation: String,
serializer: KSerializer<T>? = null,
- noinline args: (JSONObject.() -> JSONObject)? = null
+ noinline args: (JSONObject.() -> JSONObject)? = null,
): WalletResponse<T> = withContext(Dispatchers.Default) {
- suspendCoroutine { cont ->
- val json = Json {
- ignoreUnknownKeys = true
- (T::class.companionObjectInstance as? CustomClassDiscriminator)?.let {
- classDiscriminator = it.discriminator
+ val json = BackendManager.json
+ try {
+ when (val response = sendRequest(operation, args?.invoke(JSONObject()))) {
+ is ApiResponse.Response -> {
+ val t: T = serializer?.let {
+ json.decodeFromJsonElement(serializer, response.result)
+ } ?: Unit as T
+ WalletResponse.Success(t)
}
- }
- sendRequest(operation, args?.invoke(JSONObject())) { isError, message ->
- val response = try {
- if (isError) {
- val error =
- json.decodeFromString(TalerErrorInfo.serializer(), message.toString())
- WalletResponse.Error(error)
- } else {
- val t: T = serializer?.let {
- json.decodeFromString(serializer, message.toString())
- } ?: Unit as T
- WalletResponse.Success(t)
- }
- } catch (e: Exception) {
- val info = TalerErrorInfo(0, "", e.toString(), null)
- WalletResponse.Error(info)
+
+ is ApiResponse.Error -> {
+ val error: TalerErrorInfo = json.decodeFromJsonElement(response.error)
+ WalletResponse.Error(error)
}
- cont.resume(response)
}
+ } catch (e: Exception) {
+ val info = TalerErrorInfo(NONE, "", e.toString())
+ WalletResponse.Error(info)
}
}
- fun destroy() {
- // FIXME: implement this!
- }
+ // Returns raw JSON response instead of serialized object
+ suspend inline fun rawRequest(
+ operation: String,
+ noinline args: (JSONObject.() -> JSONObject)? = null,
+ ): WalletResponse<JsonObject> = withContext(Dispatchers.Default) {
+ val json = BackendManager.json
+ try {
+ when (val response = sendRequest(operation, args?.invoke(JSONObject()))) {
+ is ApiResponse.Response -> {
+ WalletResponse.Success(response.result)
+ }
- companion object {
- const val TAG = "WalletBackendApi"
+ is ApiResponse.Error -> {
+ val error: TalerErrorInfo = json.decodeFromJsonElement(response.error)
+ WalletResponse.Error(error)
+ }
+ }
+ } catch (e: Exception) {
+ val info = TalerErrorInfo(NONE, "", e.toString())
+ WalletResponse.Error(info)
+ }
}
}