- A LiveData wrapped query looking for entries of the 'transfer' table that lack a BTS value now automatically trigger a GetMarketHistory network call
- The network response is parsed and a BTS equivalent value is stored in the database
This commit is contained in:
parent
771aed9429
commit
85846b6c75
8 changed files with 172 additions and 33 deletions
|
@ -23,8 +23,22 @@ class TransfersTests {
|
|||
|
||||
@Before
|
||||
fun createDb() {
|
||||
Log.d(TAG,"createDb")
|
||||
db = Room.inMemoryDatabaseBuilder(context, BitsyDatabase::class.java).build()
|
||||
}
|
||||
|
||||
@After
|
||||
@Throws(IOException::class)
|
||||
fun closeDb(){
|
||||
Log.d(TAG,"closeDB")
|
||||
db.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the database to the testGetTransfersMissingEquivalentValues and testGetTransfersMissingEquivalentValues2
|
||||
* tests.
|
||||
*/
|
||||
private fun prepareMissingEquivalentValues(){
|
||||
// We create 2 transfers for the 'transfers' table, but only one of them will have an equivalent value entry
|
||||
val t1 = Transfer("1.11.702181910", 34118155, 1485018549, 264174, "1.3.0", "1.2.32567","1.2.139293",15869682,"1.3.0","")
|
||||
val t2 = Transfer("1.11.684483739", 33890367, 1547171166, 11030, "1.3.0", "1.2.139293","1.2.1029856",98,"1.3.120","")
|
||||
|
@ -36,12 +50,6 @@ class TransfersTests {
|
|||
db.equivalentValueDao().insert(equivalentValue)
|
||||
}
|
||||
|
||||
@After
|
||||
@Throws(IOException::class)
|
||||
fun closeDb(){
|
||||
db.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* This test makes use of the LiveData Testing library and its objective is to prove that
|
||||
* the TransferDao#getTransfersWithMissingValueIn(symbol: String) will return only the
|
||||
|
@ -52,6 +60,7 @@ class TransfersTests {
|
|||
*/
|
||||
@Test
|
||||
fun testGetTransfersMissingEquivalentValues(){
|
||||
prepareMissingEquivalentValues()
|
||||
db.transferDao()
|
||||
.getTransfersWithMissingValueIn("usd")
|
||||
.test()
|
||||
|
@ -71,6 +80,7 @@ class TransfersTests {
|
|||
*/
|
||||
@Test
|
||||
fun testGetTransfersMissingEquivalentValues2(){
|
||||
prepareMissingEquivalentValues()
|
||||
val transfers: List<Transfer> = LiveDataTestUtil.getValue(db.transferDao().getTransfersWithMissingValueIn("usd"))
|
||||
Assert.assertNotNull(transfers)
|
||||
Assert.assertEquals(1, transfers.size)
|
||||
|
@ -78,4 +88,34 @@ class TransfersTests {
|
|||
Assert.assertEquals(33890367, transfers[0].blockNumber)
|
||||
Log.d(TAG, "transfer ${transfers[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetTransfersWithMissingBtsValue(){
|
||||
val t1 = Transfer("1.11.702181910",
|
||||
34118155,
|
||||
1485018549,
|
||||
264174,
|
||||
"1.3.0",
|
||||
"1.2.32567",
|
||||
"1.2.139293",
|
||||
15869682,
|
||||
"1.3.0","")
|
||||
val t2 = Transfer("1.11.684483739",
|
||||
33890367,
|
||||
1547171166,
|
||||
11030,
|
||||
"1.3.0",
|
||||
"1.2.139293",
|
||||
"1.2.1029856",
|
||||
98,
|
||||
"1.3.120",
|
||||
"",
|
||||
1000)
|
||||
db.transferDao().insert(t1)
|
||||
db.transferDao().insert(t2)
|
||||
db.transferDao().getTransfersWithMissingBtsValue()
|
||||
.test()
|
||||
.assertHasValue()
|
||||
.assertValue { transfer -> transfer.id == "1.11.702181910" }
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
|
@ -17,6 +18,7 @@ import com.crashlytics.android.Crashlytics
|
|||
import com.crashlytics.android.core.CrashlyticsCore
|
||||
import cy.agorise.bitsybitshareswallet.BuildConfig
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Balance
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
|
@ -31,16 +33,16 @@ import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
|||
import cy.agorise.graphenej.api.android.NetworkService
|
||||
import cy.agorise.graphenej.api.android.RxBus
|
||||
import cy.agorise.graphenej.api.calls.*
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import cy.agorise.graphenej.models.BlockHeader
|
||||
import cy.agorise.graphenej.models.FullAccountDetails
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import cy.agorise.graphenej.models.*
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
|
@ -59,6 +61,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
private const val RESPONSE_GET_ACCOUNT_BALANCES = 3
|
||||
private const val RESPONSE_GET_ASSETS = 4
|
||||
private const val RESPONSE_GET_BLOCK_HEADER = 5
|
||||
private const val RESPONSE_GET_MARKET_HISTORY = 6
|
||||
}
|
||||
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
|
@ -72,8 +75,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
|
||||
private val mHandler = Handler()
|
||||
|
||||
// Disposable returned at the bus subscription
|
||||
private var mDisposable: Disposable? = null
|
||||
// Composite disposable used to clear all disposables once the activity is destroyed
|
||||
private val mCompositeDisposable = CompositeDisposable()
|
||||
|
||||
private var storedOpCount: Long = -1
|
||||
|
||||
|
@ -91,6 +94,10 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
|
||||
private var blockNumberWithMissingTime = 0L
|
||||
|
||||
// Variable used to hold a reference to the specific Transfer instance which we're currently trying
|
||||
// to resolve an equivalent BTS value
|
||||
var transfer: Transfer? = null
|
||||
|
||||
/**
|
||||
* Flag used to keep track of the NetworkService binding state
|
||||
*/
|
||||
|
@ -145,7 +152,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}
|
||||
})
|
||||
|
||||
mDisposable = RxBus.getBusInstance()
|
||||
val disposable = RxBus.getBusInstance()
|
||||
.asFlowable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
|
@ -153,6 +160,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}, {
|
||||
this.handleError(it)
|
||||
})
|
||||
mCompositeDisposable.add(disposable)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,6 +211,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
handleBlockHeader(message.result as BlockHeader, blockNumber)
|
||||
requestIdToBlockNumberMap.remove(message.id)
|
||||
}
|
||||
RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List<BucketObject>)
|
||||
}
|
||||
responseMap.remove(message.id)
|
||||
}
|
||||
|
@ -227,12 +236,33 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
} else if (message.updateCode == ConnectionStatusUpdate.API_UPDATE) {
|
||||
// If we got an API update
|
||||
if(message.api == ApiAccess.API_HISTORY) {
|
||||
//TODO: Start the procedure that will obtain the missing equivalent values
|
||||
// Starts the procedure that will obtain the missing equivalent values
|
||||
mTransferViewModel
|
||||
.getTransfersWithMissingBtsValue().observe(this, Observer<Transfer> {
|
||||
if(it != null) handleTransfersWithMissingBtsValue(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called whenever we get a list of transfers with their bts value missing.
|
||||
*/
|
||||
private fun handleTransfersWithMissingBtsValue(transfer: Transfer) {
|
||||
if(mNetworkService?.isConnected == true){
|
||||
Log.d(TAG,"Transfer: ${transfer}")
|
||||
val base = Asset(transfer.transferAssetId)
|
||||
val quote = Asset("1.3.0")
|
||||
val bucket: Long = TimeUnit.SECONDS.convert(1, TimeUnit.DAYS)
|
||||
val end: Long = transfer.timestamp * 1000L
|
||||
val start: Long = (transfer.timestamp - bucket) * 1000L
|
||||
val id = mNetworkService!!.sendMessage(GetMarketHistory(base, quote, bucket, start, end), GetMarketHistory.REQUIRED_API)
|
||||
responseMap[id] = RESPONSE_GET_MARKET_HISTORY
|
||||
this.transfer = transfer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called whenever a response to the 'get_full_accounts' API call has been detected.
|
||||
* @param accountDetails De-serialized account details object
|
||||
|
@ -281,7 +311,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}
|
||||
|
||||
private fun handleBalanceUpdate(assetAmountList: List<AssetAmount>) {
|
||||
Log.d(TAG, "handleBalanceUpdate")
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
val balances = ArrayList<Balance>()
|
||||
for (assetAmount in assetAmountList) {
|
||||
|
@ -336,6 +365,23 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleMarketData(buckets: List<BucketObject>) {
|
||||
if(buckets.isNotEmpty()){
|
||||
Log.d(TAG,"handleMarketData. Bucket is not empty")
|
||||
val bucket = buckets[0]
|
||||
val pair = Pair(transfer, bucket)
|
||||
val disposable = Observable.just(pair)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.map { mTransferViewModel.updateBtsValue(it.first!!, it.second) }
|
||||
.subscribe({},{ Log.e(TAG,"Error at updateBtsValue. Msg: ${it.message}")
|
||||
})
|
||||
mCompositeDisposable.add(disposable)
|
||||
}else{
|
||||
Log.i(TAG,"handleMarketData. Bucket IS empty")
|
||||
AsyncTask.execute { mTransferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBalances() {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()),
|
||||
|
@ -450,6 +496,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!mDisposable!!.isDisposed) mDisposable!!.dispose()
|
||||
if(!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose()
|
||||
}
|
||||
}
|
|
@ -17,4 +17,7 @@ interface AssetDao {
|
|||
|
||||
@Query("SELECT id, symbol, precision, description, issuer FROM assets INNER JOIN balances WHERE assets.id = balances.asset_id AND balances.asset_amount > 0")
|
||||
fun getAllNonZero(): LiveData<List<Asset>>
|
||||
|
||||
@Query("SELECT * FROM assets WHERE id = :assetId")
|
||||
fun getAssetDetails(assetId: String): Asset
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package cy.agorise.bitsybitshareswallet.database.daos
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.*
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||
import io.reactivex.Single
|
||||
|
||||
@Dao
|
||||
interface TransferDao {
|
||||
@Insert
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(transfer: Transfer)
|
||||
|
||||
@Update()
|
||||
fun update(transfer: Transfer)
|
||||
|
||||
// TODO find a way to return number of added rows
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insertAll(transfers: List<Transfer>)
|
||||
|
@ -29,6 +29,9 @@ interface TransferDao {
|
|||
@Query("SELECT block_number FROM transfers WHERE timestamp='0' LIMIT 1")
|
||||
fun getTransferBlockNumberWithMissingTime(): LiveData<Long>
|
||||
|
||||
@Query("SELECT * FROM transfers WHERE timestamp != 0 AND bts_value = -1 AND transfer_asset_id != '1.3.0' LIMIT 1")
|
||||
fun getTransfersWithMissingBtsValue(): LiveData<Transfer>
|
||||
|
||||
@Query("SELECT * FROM transfers WHERE id NOT IN (SELECT transfer_id FROM equivalent_values WHERE symbol = :symbol)")
|
||||
fun getTransfersWithMissingValueIn(symbol: String): LiveData<List<Transfer>>
|
||||
|
||||
|
|
|
@ -17,5 +17,12 @@ data class Transfer (
|
|||
@ColumnInfo(name = "transfer_amount") val transferAmount: Long,
|
||||
@ColumnInfo(name = "transfer_asset_id") val transferAssetId: String, // TODO should be foreign key to Asset
|
||||
@ColumnInfo(name = "memo") val memo: String,
|
||||
@ColumnInfo(name = "bts_value") val btsValue: Long? = -1
|
||||
)
|
||||
@ColumnInfo(name = "bts_value") var btsValue: Long? = Transfer.NOT_CALCULATED
|
||||
){
|
||||
companion object {
|
||||
// Constant used to specify an uninitialized BTS equivalent value
|
||||
val NOT_CALCULATED: Long? = -1L
|
||||
// Constant used to specify a BTS equivalent value whose calculation returned an error
|
||||
val ERROR: Long? = -2L
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ package cy.agorise.bitsybitshareswallet.repositories
|
|||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import androidx.lifecycle.LiveData
|
||||
import cy.agorise.bitsybitshareswallet.database.daos.AssetDao
|
||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||
import cy.agorise.bitsybitshareswallet.database.daos.AssetDao
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Asset
|
||||
|
||||
class AssetRepository internal constructor(context: Context) {
|
||||
|
@ -24,6 +24,10 @@ class AssetRepository internal constructor(context: Context) {
|
|||
insertAllAsyncTask(mAssetDao).execute(assets)
|
||||
}
|
||||
|
||||
fun getAssetDetails(assetId: String): Asset {
|
||||
return mAssetDao.getAssetDetails(assetId)
|
||||
}
|
||||
|
||||
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: AssetDao) :
|
||||
AsyncTask<List<Asset>, Void, Void>() {
|
||||
|
||||
|
|
|
@ -21,6 +21,10 @@ class TransferRepository internal constructor(context: Context) {
|
|||
insertAllAsyncTask(mTransferDao).execute(transfers)
|
||||
}
|
||||
|
||||
fun update(transfer: Transfer){
|
||||
mTransferDao.insert(transfer)
|
||||
}
|
||||
|
||||
fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
||||
setBlockTimeAsyncTask(mTransferDao).execute(Pair(blockNumber, timestamp))
|
||||
}
|
||||
|
@ -41,6 +45,10 @@ class TransferRepository internal constructor(context: Context) {
|
|||
return mTransferDao.getTransfersWithMissingValueIn(symbol)
|
||||
}
|
||||
|
||||
fun getTransfersWithMissingBtsValue(): LiveData<Transfer> {
|
||||
return mTransferDao.getTransfersWithMissingBtsValue()
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
deleteAllAsyncTask(mTransferDao).execute()
|
||||
}
|
||||
|
|
|
@ -4,24 +4,52 @@ import android.app.Application
|
|||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.google.common.primitives.UnsignedLong
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Function
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import cy.agorise.graphenej.Asset
|
||||
import cy.agorise.graphenej.AssetAmount
|
||||
import cy.agorise.graphenej.Converter
|
||||
import cy.agorise.graphenej.models.BucketObject
|
||||
|
||||
|
||||
class TransferViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val TAG = "TransferViewModel"
|
||||
private var mRepository = TransferRepository(application)
|
||||
private var mTransferRepository = TransferRepository(application)
|
||||
private var mAssetRepository = AssetRepository(application)
|
||||
|
||||
internal fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
||||
mRepository.setBlockTime(blockNumber, timestamp)
|
||||
mTransferRepository.setBlockTime(blockNumber, timestamp)
|
||||
}
|
||||
|
||||
internal fun getTransferBlockNumberWithMissingTime(): LiveData<Long> {
|
||||
return mRepository.getTransferBlockNumberWithMissingTime()
|
||||
return mTransferRepository.getTransferBlockNumberWithMissingTime()
|
||||
}
|
||||
|
||||
fun getTransfersWithMissingValueIn(symbol: String) {
|
||||
mRepository.getTransfersWithMissingValueIn(symbol)
|
||||
fun getTransfersWithMissingValueIn(symbol: String) : LiveData<List<Transfer>>{
|
||||
return mTransferRepository.getTransfersWithMissingValueIn(symbol)
|
||||
}
|
||||
|
||||
fun getTransfersWithMissingBtsValue() : LiveData<Transfer> {
|
||||
return mTransferRepository.getTransfersWithMissingBtsValue()
|
||||
}
|
||||
|
||||
fun updateBtsValue(transfer: Transfer, bucket: BucketObject) {
|
||||
val base = mAssetRepository.getAssetDetails(bucket.key.base.objectId) // Always BTS ?
|
||||
val quote = mAssetRepository.getAssetDetails(bucket.key.quote.objectId) // Any asset other than BTS
|
||||
val converter = Converter(Asset(base.id, base.symbol, base.precision), Asset(quote.id, quote.symbol, quote.precision), bucket)
|
||||
// The "base" amount is always the amount we have, and the quote would be the amount we want to obtain.
|
||||
// It can be strange that the second argument of the AssetAmount constructor here we pass the quote.id, quote.symbol and quote.precision
|
||||
// when building the "base" amount instance. But this is just because the full node will apparently always treat BTS as the base.
|
||||
val baseAmount = AssetAmount(UnsignedLong.valueOf(transfer.transferAmount), Asset(quote.id, quote.symbol, quote.precision))
|
||||
val quoteAmount = converter.convert(baseAmount, Converter.OPEN_VALUE)
|
||||
transfer.btsValue = quoteAmount
|
||||
mTransferRepository.update(transfer)
|
||||
}
|
||||
|
||||
fun updateBtsValue(transfer: Transfer, value: Long?) {
|
||||
transfer.btsValue = value
|
||||
mTransferRepository.update(transfer)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue