bitsy-wallet/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/NodeRepository.kt

129 lines
5.1 KiB
Kotlin

package cy.agorise.bitsybitshareswallet.repositories
import android.os.AsyncTask
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
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<String, Boolean> {
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<Node>) {
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
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}
private fun getDefaultFormattedNodes(): String {
return BITSHARES_NODE_URLS.joinToString(separator = ",")
}
private fun getDBFormattedNodes(nodes: List<Node>): 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<FullNode>) {
AsyncTask.execute {
nodes.forEach {
nodeDao.updateLatency(it.latencyValue.toLong(), it.url)
}
}
}
}