XYOracleNetwork/sdk-xyo-client-android

View on GitHub
sdk/src/main/java/network/xyo/client/XyoPanel.kt

Summary

Maintainability
A
0 mins
Test Coverage
package network.xyo.client

import android.content.Context
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import kotlinx.coroutines.launch
import network.xyo.client.address.XyoAccount
import network.xyo.client.archivist.api.PostBoundWitnessesResult
import network.xyo.client.archivist.api.XyoArchivistApiClient
import network.xyo.client.archivist.api.XyoArchivistApiConfig
import network.xyo.client.archivist.wrapper.ArchivistWrapper
import network.xyo.client.boundwitness.XyoBoundWitnessBuilder
import network.xyo.client.boundwitness.XyoBoundWitnessJson
import network.xyo.client.datastore.PrefsRepository
import network.xyo.client.node.client.NodeClient
import network.xyo.client.node.client.PostQueryResult
import network.xyo.client.payload.XyoPayload

data class XyoPanelReportResult(val bw: XyoBoundWitnessJson, val apiResults: List<PostBoundWitnessesResult>)
data class XyoPanelReportQueryResult(val bw: XyoBoundWitnessJson, val apiResults: List<PostQueryResult>)

class MissingNodesException(message: String) : Exception(message) {}

@RequiresApi(Build.VERSION_CODES.M)
class XyoPanel(val context: Context, private val archivists: List<XyoArchivistApiClient>, private val witnesses: List<XyoWitness<XyoPayload>>?, private val nodeUrlsAndAccounts: ArrayList<Pair<String, XyoAccount?>>?) {
    var previousHash: String? = null
    private var nodes: MutableList<NodeClient>? = null
    var defaultAccount: XyoAccount? = null

    @Deprecated("use constructors without deprecated archive field")
    constructor(
        context: Context,
        archive: String? = null,
        apiDomain: String? = null,
        witnesses: List<XyoWitness<XyoPayload>>? = null
    ) :
            this(
                context,
                listOf(
                    XyoArchivistApiClient.get(
                        XyoArchivistApiConfig(
                            archive ?: DefaultArchive,
                            apiDomain ?: DefaultApiDomain
                        )
                    )
                ),
                witnesses,
                null
            )

    constructor(
        context: Context,
        // ArrayList to not cause compiler confusion with other class constructor signatures
        nodeUrlsAndAccounts: ArrayList<Pair<String, XyoAccount?>>,
        witnesses: List<XyoWitness<XyoPayload>>? = null
    ): this(
            context,
            listOf(
                XyoArchivistApiClient.get(
                    XyoArchivistApiConfig(
                        DefaultArchive,
                        DefaultApiDomain
                    )
                )
            ),
            witnesses,
            nodeUrlsAndAccounts
        )

    constructor(
        context: Context,
        observe: ((context: Context, previousHash: String?) -> XyoEventPayload?)?
    ): this(
        context,
        arrayListOf(Pair("$DefaultApiDomain/Archivist", XyoAccount())),
        listOf(XyoWitness(observe)),
    )

    suspend fun resolveNodes(resetNodes: Boolean = false) {
        if (resetNodes) nodes = null
        this.defaultAccount = PrefsRepository(context).getAccount()
        if (nodeUrlsAndAccounts?.isNotEmpty() == true) {
            nodes = mutableListOf<NodeClient>().let {
                this@XyoPanel.nodeUrlsAndAccounts?.forEach { pair ->
                    val nodeUrl = pair.first
                    val account = pair.second ?: defaultAccount
                    it.add(NodeClient(nodeUrl, account))
                }
                it
            }
        }
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    fun event(event: String) {
        xyoScope.launch {
            this@XyoPanel.eventAsync(event)
        }
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    suspend fun eventAsync(event: String): XyoPanelReportResult {
        val adhocWitnessList = listOf(
            XyoWitness({
                _, previousHash -> XyoEventPayload(event, previousHash)
            })
        )
        return this.reportAsync(adhocWitnessList)
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    suspend fun eventAsyncQuery(event: String): XyoPanelReportQueryResult {
        val adhocWitnessList = listOf(
            XyoWitness({
                    _, previousHash -> XyoEventPayload(event, previousHash)
            })
        )
        return reportAsyncQuery(adhocWitnessList)
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    fun report(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()) {
        xyoScope.launch {
            reportAsync(adhocWitnesses)
        }
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    fun reportQuery(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()) {
        xyoScope.launch {
            reportAsyncQuery(adhocWitnesses)
        }
    }

    private fun generateBoundWitnessJson(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()): XyoBoundWitnessJson {
        val witnesses: List<XyoWitness<XyoPayload>> = (this.witnesses ?: emptyList()).plus(adhocWitnesses)
        val payloads = generatePayloads()
        return XyoBoundWitnessBuilder()
            .payloads(payloads)
            .witnesses(witnesses)
            .build(previousHash)
    }


    private fun generatePayloads(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()): List<XyoPayload> {
        val witnesses: List<XyoWitness<XyoPayload>> = (this.witnesses ?: emptyList()).plus(adhocWitnesses)
        val payloads = witnesses.map { witness ->
            witness.observe(context)
        }
        return payloads.mapNotNull { payload -> payload }
    }

    @Deprecated("use reportAsyncQuery instead")
    @kotlinx.coroutines.ExperimentalCoroutinesApi
    suspend fun reportAsync(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()): XyoPanelReportResult {
        val bw = generateBoundWitnessJson(adhocWitnesses)
        previousHash = bw._hash
        val results = mutableListOf<PostBoundWitnessesResult>()
        archivists.forEach { archivist ->
            results.add(archivist.postBoundWitnessAsync(bw))
        }
        return XyoPanelReportResult(bw, results)
    }

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    suspend fun reportAsyncQuery(adhocWitnesses: List<XyoWitness<XyoPayload>> = emptyList()): XyoPanelReportQueryResult {
        if (nodes == null) resolveNodes()
        val bw = generateBoundWitnessJson()
        val payloads = generatePayloads(adhocWitnesses)
        val results = mutableListOf<PostQueryResult>()

        if (nodes.isNullOrEmpty()) {
            val message = "Called reportAsyncQuery without first constructing any nodeClients.  Try passing nodeUrls?"
            Log.e("xyoClient", message)
            throw MissingNodesException(message)
        }

        nodes?.forEach { node ->
            val archivist = ArchivistWrapper(node)
            val queryResult = archivist.insert(payloads, previousHash)
            results.add(queryResult)
        }
        return XyoPanelReportQueryResult(bw, results)
    }

    companion object {
        const val DefaultApiDomain = "https://api.archivist.xyo.network"
        const val DefaultArchive = "temp"
    }
}