package cy.agorise.bitsybitshareswallet.repositories import android.os.AsyncTask import android.util.Log import com.crashlytics.android.Crashlytics import cy.agorise.bitsybitshareswallet.database.daos.NodeDao import cy.agorise.bitsybitshareswallet.database.entities.Node import cy.agorise.bitsybitshareswallet.network.BitsyWebservice import cy.agorise.bitsybitshareswallet.network.ServiceGenerator import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.graphenej.network.FullNode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class NodeRepository(private val nodeDao: NodeDao) { companion object { private const val TAG = "NodeRepository" // Minimum number of nodes required to update the nodes db table. private const val MIN_NODES_SIZE = 3 // List of BitShares nodes the app will try to connect to var BITSHARES_NODE_URLS = arrayOf( // PP private nodes "wss://nl.palmpay.io/ws", // Other public nodes "wss://btsws.roelandp.nl/ws", "wss://api.bts.mobi/ws", "wss://kimziv.com/ws", "wss://api.bts.ai") } private val mBitsyWebservice: BitsyWebservice? init { val sg = ServiceGenerator(Constants.BITSY_WEBSERVICE_URL) mBitsyWebservice = sg.getService(BitsyWebservice::class.java) } /** * Returns a Pair of items: * First. A list of comma separated node urls in form of a string. The node urls come from the * database if the nodes table is already populated, else a default list is used. * Second. A Boolean that specifies if the app should try to autoConnect immediately, or wait * for other event to launch the connect method. */ suspend fun getFormattedNodes(): Pair { val nodes = nodeDao.getSortedNodes() // Launches a job to refresh the list of nodes into the database, without blocking the // execution of this function, so that the formatted nodes can be returned immediately // without waiting until the nodes have been updated in the database. CoroutineScope(Dispatchers.Default).launch { refreshNodes(nodes) } return if (nodes.size < MIN_NODES_SIZE) { // If the nodes db table is empty or very small, it could mean that the nodes have not // still been updated from the webservice, thus returning a default list of nodes as a fallback. // False is returned since we want to verify the node latencies before choosing the best // one and trying to connect to it. Pair(getDefaultFormattedNodes(), false) } else { // Use the list of nodes stored in the database. True is returned since the list of nodes // is already ordered by latency, and we don't need to wait to obtain the latency // readings, thus the app can immediately try to connect to the first node in the list. Pair(getDBFormattedNodes(nodes), true) } } /** * Verifies if the nodes information should be updated and if true, fetches the nodes * information from the webservice and updates the database */ private suspend fun refreshNodes(nodes: List) { val now = System.currentTimeMillis() / 1000 val lastUpdate: Long = if (nodes.size < MIN_NODES_SIZE) { 0 } else { nodes[0].lastUpdate } val updatePeriod = Constants.NODES_UPDATE_PERIOD // Verify if nodes list should be updated if (now - updatePeriod > lastUpdate) { try { val response = mBitsyWebservice?.getNodes() // Update the list of nodes only if we got at least MIN_NODES_SIZE nodes if (response?.isSuccessful == true && (response.body()?.size ?: 0) >= MIN_NODES_SIZE) { val nodesWS = response.body() ?: return val nodesDB = nodesWS.map { Node(url = it.url, lastUpdate = now) } Log.d(TAG, "Updating the list of nodes.") nodeDao.updateNodes(nodesDB, now) } } catch (e: Exception) { // Generic exception handling Crashlytics.logException(e) } } } private fun getDefaultFormattedNodes(): String { return BITSHARES_NODE_URLS.joinToString(separator = ",") } private fun getDBFormattedNodes(nodes: List): String { return nodes.joinToString(separator = ",") { it.url } } /** * Function that will receive an up-to-date list of FullNode instances and persist it on * the database. * * @param nodes List of nodes with fresh latency measurements. */ fun updateNodeLatencies(nodes: List) { AsyncTask.execute { nodes.forEach { nodeDao.updateLatency(it.latencyValue.toLong(), it.url) } } } }