Create nodes database table.

- Added the library dependency to use Kotlin Coroutines with Room.
- Created the Node Room Entity, which will create the nodes database table.
- Created NodeDao, which will be responsible to access the Room entity (nodes database table).
- Created a Room database migration, to create the nodes database table in existing instalations updating to the new version.
master
Severiano Jaramillo 2019-08-23 12:48:58 -05:00
parent 3841f53b14
commit b077de95ac
7 changed files with 559 additions and 5 deletions

View File

@ -84,8 +84,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// AAC Room
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-rxjava2:$room_version" // RxJava support for Room
implementation "androidx.room:room-ktx:$room_version" // Coroutines support for Room
kapt "androidx.room:room-compiler:$room_version"
// AAC Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

View File

@ -0,0 +1,477 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "0eafc3ef99fd9e53dfefdaef0547e8ff",
"entities": [
{
"tableName": "assets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `precision` INTEGER NOT NULL, `description` TEXT NOT NULL, `issuer` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "symbol",
"columnName": "symbol",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "precision",
"columnName": "precision",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "issuer",
"columnName": "issuer",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "authorities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `authority_type` INTEGER NOT NULL, `encrypted_wif` TEXT NOT NULL, `encrypted_brain_key` TEXT NOT NULL, `encrypted_sequence_number` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "authorityType",
"columnName": "authority_type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "encryptedWIF",
"columnName": "encrypted_wif",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedBrainKey",
"columnName": "encrypted_brain_key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedSequenceNumber",
"columnName": "encrypted_sequence_number",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "balances",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `asset_amount` INTEGER NOT NULL, `last_update` INTEGER NOT NULL, PRIMARY KEY(`asset_id`))",
"fields": [
{
"fieldPath": "assetId",
"columnName": "asset_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "assetAmount",
"columnName": "asset_amount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"asset_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "equivalent_values",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`transfer_id` TEXT NOT NULL, `value` INTEGER NOT NULL, `symbol` TEXT NOT NULL, PRIMARY KEY(`transfer_id`, `symbol`), FOREIGN KEY(`transfer_id`) REFERENCES `transfers`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "transferId",
"columnName": "transfer_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "symbol",
"columnName": "symbol",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"transfer_id",
"symbol"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "transfers",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"transfer_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "transfers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `block_number` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `fee_amount` INTEGER NOT NULL, `fee_asset_id` TEXT NOT NULL, `source` TEXT NOT NULL, `destination` TEXT NOT NULL, `transfer_amount` INTEGER NOT NULL, `transfer_asset_id` TEXT NOT NULL, `memo` TEXT NOT NULL, `bts_value` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "blockNumber",
"columnName": "block_number",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "feeAmount",
"columnName": "fee_amount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "feeAssetId",
"columnName": "fee_asset_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "destination",
"columnName": "destination",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "transferAmount",
"columnName": "transfer_amount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "transferAssetId",
"columnName": "transfer_asset_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "memo",
"columnName": "memo",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "btsValue",
"columnName": "bts_value",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "user_accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `is_ltm` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isLtm",
"columnName": "is_ltm",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "merchants",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `address` TEXT, `lat` REAL NOT NULL, `lon` REAL NOT NULL, `phone` TEXT, `telegram` TEXT, `website` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "_id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lat",
"columnName": "lat",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "lon",
"columnName": "lon",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "phone",
"columnName": "phone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "telegram",
"columnName": "telegram",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "tellers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `address` TEXT, `lat` REAL NOT NULL, `lon` REAL NOT NULL, `phone` TEXT, `telegram` TEXT, `keybase` TEXT, `whatsapp` TEXT, `viber` TEXT, `email` TEXT, `website` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "_id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gt_name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lat",
"columnName": "lat",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "lon",
"columnName": "lon",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "phone",
"columnName": "phone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "telegram",
"columnName": "telegram",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "keybase",
"columnName": "keybase",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "whatsapp",
"columnName": "whatsapp",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viber",
"columnName": "viber",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "email",
"columnName": "email",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "website",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "nodes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `latency` INTEGER NOT NULL, `last_update` INTEGER NOT NULL, PRIMARY KEY(`url`))",
"fields": [
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "latency",
"columnName": "latency",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"url"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0eafc3ef99fd9e53dfefdaef0547e8ff')"
]
}
}

View File

@ -19,9 +19,10 @@ import cy.agorise.bitsybitshareswallet.database.joins.TransferDetailDao
Transfer::class,
UserAccount::class,
Merchant::class,
Teller::class
Teller::class,
Node::class
],
version = 4,
version = 5,
exportSchema = true)
abstract class BitsyDatabase : RoomDatabase() {
abstract fun assetDao(): AssetDao
@ -34,6 +35,7 @@ abstract class BitsyDatabase : RoomDatabase() {
abstract fun transferDetailDao(): TransferDetailDao
abstract fun merchantDao(): MerchantDao
abstract fun tellerDao(): TellerDao
abstract fun nodeDao(): NodeDao
companion object {
@ -50,6 +52,7 @@ abstract class BitsyDatabase : RoomDatabase() {
).addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_5)
.build()
}
}
@ -57,7 +60,7 @@ abstract class BitsyDatabase : RoomDatabase() {
return INSTANCE
}
val MIGRATION_1_2 = object : Migration(1, 2) {
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS 'merchants' ('id' TEXT NOT NULL PRIMARY KEY, 'name' TEXT NOT NULL, 'address' TEXT, 'lat' REAL NOT NULL, 'lon' REAL NOT NULL, 'phone' TEXT, 'telegram' TEXT, 'website' TEXT)")
database.execSQL("CREATE TABLE IF NOT EXISTS 'tellers' ('id' TEXT NOT NULL PRIMARY KEY, 'name' TEXT NOT NULL, 'address' TEXT, 'lat' REAL NOT NULL, 'lon' REAL NOT NULL, 'phone' TEXT, 'telegram' TEXT, 'website' TEXT)")
@ -78,11 +81,17 @@ abstract class BitsyDatabase : RoomDatabase() {
}
}
val MIGRATION_3_4 = object : Migration(3,4) {
private val MIGRATION_3_4 = object : Migration(3,4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE tellers")
database.execSQL("CREATE TABLE IF NOT EXISTS tellers (id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, address TEXT, lat REAL NOT NULL, lon REAL NOT NULL, phone TEXT, telegram TEXT, keybase TEXT, whatsapp TEXT, viber TEXT, email TEXT, website TEXT)")
}
}
private val MIGRATION_4_5 = object : Migration(4,5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS nodes (url TEXT NOT NULL PRIMARY KEY, latency INTEGER NOT NULL, last_update INTEGER NOT NULL)")
}
}
}
}

View File

@ -0,0 +1,46 @@
package cy.agorise.bitsybitshareswallet.database.daos
import androidx.room.*
import cy.agorise.bitsybitshareswallet.database.entities.Node
@Dao
abstract class NodeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(nodes: Node)
@Query("SELECT * FROM nodes ORDER BY latency ASC")
abstract suspend fun getSortedNodes(): List<Node>
@Query("UPDATE nodes SET latency = :newLatency WHERE url = :queryUrl")
abstract fun updateLatency(newLatency: Long, queryUrl: String)
@Query("UPDATE nodes SET last_update=:lastUpdate WHERE url=:url")
abstract suspend fun updateNode(url: String, lastUpdate: Long)
@Query("SELECT * FROM nodes WHERE url=:url")
abstract suspend fun get(url: String): Node?
@Query("DELETE FROM nodes WHERE last_update != :timestamp")
abstract suspend fun deleteOutdatedNodes(timestamp: Long)
/**
* Updates the list of nodes stored in the database in two steps:
* 1. - If a node does not already exist in the database then it just creates(inserts) a new entry.
* - If a nodes does exist then it only updates its lastUpdate field, so that it does not get
* removed in the last step, leaving the latency untouched.
* 2. Deletes all the nodes that are stored in the database, but were not updated in the previous
* step.
*/
@Transaction
open suspend fun updateNodes(nodes: List<Node>, timestamp: Long) {
for (node in nodes) {
if (get(node.url) == null) {
insert(node)
} else {
updateNode(node.url, node.lastUpdate)
}
}
deleteOutdatedNodes(timestamp)
}
}

View File

@ -0,0 +1,13 @@
package cy.agorise.bitsybitshareswallet.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "nodes")
data class Node(
@PrimaryKey
@ColumnInfo(name = "url") var url: String,
@ColumnInfo(name = "latency") var latency: Long = Long.MAX_VALUE,
@ColumnInfo(name = "last_update") var lastUpdate: Long = 0L
)

View File

@ -0,0 +1,7 @@
package cy.agorise.bitsybitshareswallet.repositories
import cy.agorise.bitsybitshareswallet.database.daos.NodeDao
class NodeRepository(private val nodeDao: NodeDao) {
}

View File

@ -7,6 +7,7 @@ import cy.agorise.graphenej.api.android.NetworkServiceManager
import io.reactivex.plugins.RxJavaPlugins
@Suppress("unused")
class BitsyApplication : Application() {
companion object {