Merge pull request #2506 from buessow/dev
Add local storage of heart rate values.
This commit is contained in:
commit
c29782e0f4
|
@ -7,6 +7,7 @@ plugins {
|
|||
}
|
||||
|
||||
apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
|
||||
|
||||
android {
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package info.nightscout.database.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import info.nightscout.database.entities.embedments.InterfaceIDs
|
||||
import info.nightscout.database.entities.interfaces.DBEntryWithTimeAndDuration
|
||||
import info.nightscout.database.entities.interfaces.TraceableDBEntry
|
||||
import java.util.*
|
||||
|
||||
/** Heart rate values measured by a user smart watch or the like. */
|
||||
@Entity(
|
||||
tableName = TABLE_HEART_RATE,
|
||||
indices = [Index("id"), Index("timestamp")]
|
||||
)
|
||||
data class HeartRate(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
override var id: Long = 0,
|
||||
/** Duration milliseconds */
|
||||
override var duration: Long,
|
||||
/** Milliseconds since the epoch. End of the sampling period, i.e. the value is
|
||||
* sampled from timestamp-duration to timestamp. */
|
||||
override var timestamp: Long,
|
||||
var beatsPerMinute: Double,
|
||||
/** Source device that measured the heart rate. */
|
||||
var device: String,
|
||||
override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
|
||||
override var version: Int = 0,
|
||||
override var dateCreated: Long = -1,
|
||||
override var isValid: Boolean = true,
|
||||
override var referenceId: Long? = null,
|
||||
@Embedded
|
||||
override var interfaceIDs_backing: InterfaceIDs? = null
|
||||
) : TraceableDBEntry, DBEntryWithTimeAndDuration {
|
||||
|
||||
fun contentEqualsTo(other: HeartRate): Boolean {
|
||||
return this === other || (
|
||||
duration == other.duration &&
|
||||
timestamp == other.timestamp &&
|
||||
beatsPerMinute == other.beatsPerMinute &&
|
||||
isValid == other.isValid)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ const val TABLE_CARBS = "carbs"
|
|||
const val TABLE_DEVICE_STATUS = "deviceStatus"
|
||||
const val TABLE_EFFECTIVE_PROFILE_SWITCHES = "effectiveProfileSwitches"
|
||||
const val TABLE_EXTENDED_BOLUSES = "extendedBoluses"
|
||||
const val TABLE_HEART_RATE = "heartRate"
|
||||
const val TABLE_GLUCOSE_VALUES = "glucoseValues"
|
||||
const val TABLE_FOODS = "foods"
|
||||
const val TABLE_MULTIWAVE_BOLUS_LINKS = "multiwaveBolusLinks"
|
||||
|
|
|
@ -8,6 +8,7 @@ import info.nightscout.database.entities.Carbs
|
|||
import info.nightscout.database.entities.EffectiveProfileSwitch
|
||||
import info.nightscout.database.entities.ExtendedBolus
|
||||
import info.nightscout.database.entities.GlucoseValue
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
import info.nightscout.database.entities.MultiwaveBolusLink
|
||||
import info.nightscout.database.entities.OfflineEvent
|
||||
import info.nightscout.database.entities.PreferenceChange
|
||||
|
@ -35,5 +36,6 @@ data class NewEntries(
|
|||
val temporaryTarget: List<TemporaryTarget>,
|
||||
val therapyEvents: List<TherapyEvent>,
|
||||
val totalDailyDoses: List<TotalDailyDose>,
|
||||
val versionChanges: List<VersionChange>
|
||||
val versionChanges: List<VersionChange>,
|
||||
val heartRates: List<HeartRate>,
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
package info.nightscout.database.entities
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class HeartRateTest {
|
||||
@Test
|
||||
fun contentEqualsTo_equals() {
|
||||
val hr1 = createHeartRate()
|
||||
assertTrue(hr1.contentEqualsTo(hr1))
|
||||
assertTrue(hr1.contentEqualsTo(hr1.copy()))
|
||||
assertTrue(hr1.contentEqualsTo(hr1.copy (id = 2, version = 2, dateCreated = 1L, referenceId = 4L)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentEqualsTo_notEquals() {
|
||||
val hr1 = createHeartRate()
|
||||
assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L)))
|
||||
assertFalse(hr1.contentEqualsTo(hr1.copy(timestamp = 2L)))
|
||||
assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L)))
|
||||
assertFalse(hr1.contentEqualsTo(hr1.copy(beatsPerMinute = 100.0)))
|
||||
assertFalse(hr1.contentEqualsTo(hr1.copy(isValid = false)))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) =
|
||||
HeartRate(
|
||||
timestamp = timestamp ?: System.currentTimeMillis(),
|
||||
duration = 60_0000L,
|
||||
beatsPerMinute = beatsPerMinute,
|
||||
device = "T",
|
||||
)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ plugins {
|
|||
|
||||
apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/main/android_module_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
|
||||
apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
|
||||
|
||||
android {
|
||||
|
||||
|
@ -20,6 +22,9 @@ android {
|
|||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -44,6 +49,8 @@ dependencies {
|
|||
|
||||
api "com.google.dagger:dagger-android:$dagger_version"
|
||||
api "com.google.dagger:dagger-android-support:$dagger_version"
|
||||
|
||||
androidTestImplementation "androidx.room:room-testing:$room_version"
|
||||
}
|
||||
|
||||
allOpen {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 23,
|
||||
"identityHash": "173734db5f4f35f6295ed953d8124794",
|
||||
"identityHash": "a3ee37800b6cda170d0ea64799ed7876",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "apsResults",
|
||||
|
@ -3689,12 +3689,153 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "heartRate",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `duration` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `beatsPerMinute` REAL NOT NULL, `device` TEXT NOT NULL, `utcOffset` INTEGER NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "beatsPerMinute",
|
||||
"columnName": "beatsPerMinute",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "device",
|
||||
"columnName": "device",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "utcOffset",
|
||||
"columnName": "utcOffset",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dateCreated",
|
||||
"columnName": "dateCreated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isValid",
|
||||
"columnName": "isValid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "referenceId",
|
||||
"columnName": "referenceId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.nightscoutSystemId",
|
||||
"columnName": "nightscoutSystemId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.nightscoutId",
|
||||
"columnName": "nightscoutId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.pumpType",
|
||||
"columnName": "pumpType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.pumpSerial",
|
||||
"columnName": "pumpSerial",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.temporaryId",
|
||||
"columnName": "temporaryId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.pumpId",
|
||||
"columnName": "pumpId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.startId",
|
||||
"columnName": "startId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "interfaceIDs_backing.endId",
|
||||
"columnName": "endId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_heartRate_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `${TABLE_NAME}` (`id`)"
|
||||
},
|
||||
{
|
||||
"name": "index_heartRate_timestamp",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"timestamp"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
|
||||
}
|
||||
],
|
||||
"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, '173734db5f4f35f6295ed953d8124794')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a3ee37800b6cda170d0ea64799ed7876')"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,108 @@
|
|||
package info.nightscout.database.impl
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.room.testing.MigrationTestHelper
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
import info.nightscout.database.entities.TABLE_HEART_RATE
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class HeartRateDaoTest {
|
||||
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
private fun createDatabase() =
|
||||
Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
|
||||
|
||||
private fun getDbObjects(supportDb: SupportSQLiteDatabase, type: String): Set<String> {
|
||||
val names = mutableSetOf<String>()
|
||||
supportDb.query("SELECT name FROM sqlite_master WHERE type = '$type'").use { c ->
|
||||
while (c.moveToNext()) names.add(c.getString(0))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
private fun getTableNames(db: SupportSQLiteDatabase) = getDbObjects(db, "table")
|
||||
private fun getIndexNames(db: SupportSQLiteDatabase) = getDbObjects(db, "index")
|
||||
|
||||
private fun insertAndFind(database: AppDatabase) {
|
||||
val hr1 = createHeartRate()
|
||||
val id = database.heartRateDao.insert(hr1)
|
||||
val hr2 = database.heartRateDao.findById(id)
|
||||
assertTrue(hr1.contentEqualsTo(hr2!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun new_insertAndFind() {
|
||||
createDatabase().use { db -> insertAndFind(db) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migrate_createsTableAndIndices() {
|
||||
val helper = MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
AppDatabase::class.java
|
||||
)
|
||||
val startVersion = 22
|
||||
val supportDb = helper.createDatabase(TEST_DB_NAME, startVersion)
|
||||
assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE))
|
||||
DatabaseModule().migrations.filter { m -> m.startVersion >= startVersion }.forEach { m -> m.migrate(supportDb) }
|
||||
assertTrue(getTableNames(supportDb).contains(TABLE_HEART_RATE))
|
||||
assertTrue(getIndexNames(supportDb).contains("index_heartRate_id"))
|
||||
assertTrue(getIndexNames(supportDb).contains("index_heartRate_timestamp"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migrate_insertAndFind() {
|
||||
val helper = MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
AppDatabase::class.java
|
||||
)
|
||||
// Create the database for version 22 (that's missing the heartRate table).
|
||||
// helper.createDatabase removes the db file if it already exists.
|
||||
val supportDb = helper.createDatabase(TEST_DB_NAME, 22)
|
||||
assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE))
|
||||
// Room.databaseBuilder will use the previously created db file that has version 22.
|
||||
Room.databaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java, TEST_DB_NAME)
|
||||
.addMigrations(*DatabaseModule().migrations)
|
||||
.build().use { db -> insertAndFind(db) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFromTime() {
|
||||
createDatabase().use { db ->
|
||||
val dao = db.heartRateDao
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val hr1 = createHeartRate(timestamp = timestamp, beatsPerMinute = 80.0)
|
||||
val hr2 = createHeartRate(timestamp = timestamp + 1, beatsPerMinute = 150.0)
|
||||
dao.insertNewEntry(hr1)
|
||||
dao.insertNewEntry(hr2)
|
||||
|
||||
assertEquals(listOf(hr1, hr2), dao.getFromTime(timestamp))
|
||||
assertEquals(listOf(hr2), dao.getFromTime(timestamp + 1))
|
||||
assertTrue(dao.getFromTime(timestamp + 2).isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TEST_DB_NAME = "testDatabase"
|
||||
|
||||
fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) =
|
||||
HeartRate(
|
||||
timestamp = timestamp ?: System.currentTimeMillis(),
|
||||
duration = 60_0000L,
|
||||
beatsPerMinute = beatsPerMinute,
|
||||
device = "T",
|
||||
)
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package info.nightscout.database.impl.transactions
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import info.nightscout.database.impl.AppDatabase
|
||||
import info.nightscout.database.impl.AppRepository
|
||||
import info.nightscout.database.impl.HeartRateDaoTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class InsertOrUpdateHeartRateTransactionTest {
|
||||
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var repo: AppRepository
|
||||
|
||||
@Before
|
||||
fun setupUp() {
|
||||
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
|
||||
repo = AppRepository(db)
|
||||
}
|
||||
|
||||
@After
|
||||
fun shutdown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewEntry() {
|
||||
val hr1 = HeartRateDaoTest.createHeartRate()
|
||||
val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr1)).blockingGet()
|
||||
assertEquals(listOf(hr1), result.inserted)
|
||||
assertTrue(result.updated.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateEntry() {
|
||||
val hr1 = HeartRateDaoTest.createHeartRate()
|
||||
val id = db.heartRateDao.insertNewEntry(hr1)
|
||||
assertNotEquals(0, id)
|
||||
val hr2 = hr1.copy(id = id, beatsPerMinute = 181.0)
|
||||
val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr2)).blockingGet()
|
||||
assertEquals(listOf(hr2), result.updated)
|
||||
assertTrue(result.inserted.isEmpty())
|
||||
|
||||
val hr3 = db.heartRateDao.findById(id)!!
|
||||
assertTrue(hr2.contentEqualsTo(hr3))
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ import info.nightscout.database.entities.EffectiveProfileSwitch
|
|||
import info.nightscout.database.entities.ExtendedBolus
|
||||
import info.nightscout.database.entities.Food
|
||||
import info.nightscout.database.entities.GlucoseValue
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
import info.nightscout.database.entities.MultiwaveBolusLink
|
||||
import info.nightscout.database.entities.OfflineEvent
|
||||
import info.nightscout.database.entities.PreferenceChange
|
||||
|
@ -43,18 +44,20 @@ import info.nightscout.database.entities.TherapyEvent
|
|||
import info.nightscout.database.entities.TotalDailyDose
|
||||
import info.nightscout.database.entities.UserEntry
|
||||
import info.nightscout.database.entities.VersionChange
|
||||
import info.nightscout.database.impl.daos.HeartRateDao
|
||||
import java.io.Closeable
|
||||
|
||||
const val DATABASE_VERSION = 23
|
||||
const val DATABASE_VERSION = 24
|
||||
|
||||
@Database(version = DATABASE_VERSION,
|
||||
entities = [APSResult::class, Bolus::class, BolusCalculatorResult::class, Carbs::class,
|
||||
EffectiveProfileSwitch::class, ExtendedBolus::class, GlucoseValue::class, ProfileSwitch::class,
|
||||
TemporaryBasal::class, TemporaryTarget::class, TherapyEvent::class, TotalDailyDose::class, APSResultLink::class,
|
||||
MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class, UserEntry::class,
|
||||
Food::class, DeviceStatus::class, OfflineEvent::class],
|
||||
Food::class, DeviceStatus::class, OfflineEvent::class, HeartRate::class],
|
||||
exportSchema = true)
|
||||
@TypeConverters(Converters::class)
|
||||
internal abstract class AppDatabase : RoomDatabase() {
|
||||
internal abstract class AppDatabase : Closeable, RoomDatabase() {
|
||||
|
||||
abstract val glucoseValueDao: GlucoseValueDao
|
||||
|
||||
|
@ -96,4 +99,5 @@ internal abstract class AppDatabase : RoomDatabase() {
|
|||
|
||||
abstract val offlineEventDao: OfflineEventDao
|
||||
|
||||
abstract val heartRateDao: HeartRateDao
|
||||
}
|
|
@ -101,6 +101,7 @@ import kotlin.math.roundToInt
|
|||
//database.foodDao.deleteOlderThan(than)
|
||||
removed.add(Pair("DeviceStatus", database.deviceStatusDao.deleteOlderThan(than)))
|
||||
removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteOlderThan(than)))
|
||||
removed.add(Pair("HeartRate", database.heartRateDao.deleteOlderThan(than)))
|
||||
|
||||
if (deleteTrackedChanges) {
|
||||
removed.add(Pair("GlucoseValue", database.glucoseValueDao.deleteTrackedChanges()))
|
||||
|
@ -119,6 +120,7 @@ import kotlin.math.roundToInt
|
|||
removed.add(Pair("ApsResult", database.apsResultDao.deleteTrackedChanges()))
|
||||
//database.foodDao.deleteHistory()
|
||||
removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteTrackedChanges()))
|
||||
removed.add(Pair("HeartRate", database.heartRateDao.deleteTrackedChanges()))
|
||||
}
|
||||
val ret = StringBuilder()
|
||||
removed
|
||||
|
@ -930,6 +932,8 @@ import kotlin.math.roundToInt
|
|||
fun getLastOfflineEventId(): Long? =
|
||||
database.offlineEventDao.getLastId()
|
||||
|
||||
fun getHeartRatesFromTime(timeMillis: Long) = database.heartRateDao.getFromTime(timeMillis)
|
||||
|
||||
suspend fun collectNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int) = NewEntries(
|
||||
apsResults = database.apsResultDao.getNewEntriesSince(since, until, limit, offset),
|
||||
apsResultLinks = database.apsResultLinkDao.getNewEntriesSince(since, until, limit, offset),
|
||||
|
@ -948,6 +952,7 @@ import kotlin.math.roundToInt
|
|||
therapyEvents = database.therapyEventDao.getNewEntriesSince(since, until, limit, offset),
|
||||
totalDailyDoses = database.totalDailyDoseDao.getNewEntriesSince(since, until, limit, offset),
|
||||
versionChanges = database.versionChangeDao.getNewEntriesSince(since, until, limit, offset),
|
||||
heartRates = database.heartRateDao.getNewEntriesSince(since, until, limit, offset),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -956,4 +961,3 @@ inline fun <reified T : Any> Maybe<T>.toWrappedSingle(): Single<ValueWrapper<T>>
|
|||
this.map { ValueWrapper.Existing(it) as ValueWrapper<T> }
|
||||
.switchIfEmpty(Maybe.just(ValueWrapper.Absent()))
|
||||
.toSingle()
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package info.nightscout.database.impl
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase.Callback
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import info.nightscout.database.entities.TABLE_HEART_RATE
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -22,13 +24,7 @@ open class DatabaseModule {
|
|||
internal fun provideAppDatabase(context: Context, @DbFileName fileName: String) =
|
||||
Room
|
||||
.databaseBuilder(context, AppDatabase::class.java, fileName)
|
||||
// .addMigrations(migration5to6)
|
||||
// .addMigrations(migration6to7)
|
||||
// .addMigrations(migration7to8)
|
||||
// .addMigrations(migration11to12)
|
||||
.addMigrations(migration20to21)
|
||||
.addMigrations(migration21to22)
|
||||
.addMigrations(migration22to23)
|
||||
.addMigrations(*migrations)
|
||||
.addCallback(object : Callback() {
|
||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
||||
super.onOpen(db)
|
||||
|
@ -89,4 +85,36 @@ open class DatabaseModule {
|
|||
}
|
||||
}
|
||||
|
||||
private val migration23to24 = object : Migration(23, 24) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `$TABLE_HEART_RATE` (
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`duration` INTEGER NOT NULL,
|
||||
`timestamp` INTEGER NOT NULL,
|
||||
`beatsPerMinute` REAL NOT NULL,
|
||||
`device` TEXT NOT NULL,
|
||||
`utcOffset` INTEGER NOT NULL,
|
||||
`version` INTEGER NOT NULL,
|
||||
`dateCreated` INTEGER NOT NULL,
|
||||
`isValid` INTEGER NOT NULL,
|
||||
`referenceId` INTEGER,
|
||||
`nightscoutSystemId` TEXT,
|
||||
`nightscoutId` TEXT,
|
||||
`pumpType` TEXT,
|
||||
`pumpSerial` TEXT,
|
||||
`temporaryId` INTEGER,
|
||||
`pumpId` INTEGER, `startId` INTEGER,
|
||||
`endId` INTEGER)""".trimIndent()
|
||||
)
|
||||
database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `$TABLE_HEART_RATE` (`id`)""")
|
||||
database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `$TABLE_HEART_RATE` (`timestamp`)""")
|
||||
// Custom indexes must be dropped on migration to pass room schema checking after upgrade
|
||||
dropCustomIndexes(database)
|
||||
}
|
||||
}
|
||||
|
||||
/** List of all migrations for easy reply in tests. */
|
||||
@VisibleForTesting
|
||||
internal val migrations = arrayOf(migration20to21, migration21to22, migration22to23, migration23to24)
|
||||
}
|
|
@ -41,6 +41,8 @@ import info.nightscout.database.impl.daos.delegated.DelegatedTotalDailyDoseDao
|
|||
import info.nightscout.database.impl.daos.delegated.DelegatedUserEntryDao
|
||||
import info.nightscout.database.impl.daos.delegated.DelegatedVersionChangeDao
|
||||
import info.nightscout.database.entities.interfaces.DBEntry
|
||||
import info.nightscout.database.impl.daos.HeartRateDao
|
||||
import info.nightscout.database.impl.daos.delegated.DelegatedHeartRateDao
|
||||
|
||||
internal class DelegatedAppDatabase(val changes: MutableList<DBEntry>, val database: AppDatabase) {
|
||||
|
||||
|
@ -64,5 +66,6 @@ internal class DelegatedAppDatabase(val changes: MutableList<DBEntry>, val datab
|
|||
val foodDao: FoodDao = DelegatedFoodDao(changes, database.foodDao)
|
||||
val deviceStatusDao: DeviceStatusDao = DelegatedDeviceStatusDao(changes, database.deviceStatusDao)
|
||||
val offlineEventDao: OfflineEventDao = DelegatedOfflineEventDao(changes, database.offlineEventDao)
|
||||
val heartRateDao: HeartRateDao = DelegatedHeartRateDao(changes, database.heartRateDao)
|
||||
fun clearAllTables() = database.clearAllTables()
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package info.nightscout.database.impl.daos
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
import info.nightscout.database.entities.TABLE_HEART_RATE
|
||||
|
||||
@Dao
|
||||
internal interface HeartRateDao : TraceableDao<HeartRate> {
|
||||
|
||||
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE id = :id")
|
||||
override fun findById(id: Long): HeartRate?
|
||||
|
||||
@Query("DELETE FROM $TABLE_HEART_RATE")
|
||||
override fun deleteAllEntries()
|
||||
|
||||
@Query("DELETE FROM $TABLE_HEART_RATE WHERE timestamp < :than")
|
||||
override fun deleteOlderThan(than: Long): Int
|
||||
|
||||
@Query("DELETE FROM $TABLE_HEART_RATE WHERE referenceId IS NOT NULL")
|
||||
override fun deleteTrackedChanges(): Int
|
||||
|
||||
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp >= :timestamp ORDER BY timestamp")
|
||||
fun getFromTime(timestamp: Long): List<HeartRate>
|
||||
|
||||
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp > :since AND timestamp <= :until LIMIT :limit OFFSET :offset")
|
||||
fun getNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int): List<HeartRate>
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package info.nightscout.database.impl.daos.delegated
|
||||
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
import info.nightscout.database.entities.interfaces.DBEntry
|
||||
import info.nightscout.database.impl.daos.HeartRateDao
|
||||
|
||||
internal class DelegatedHeartRateDao(
|
||||
changes: MutableList<DBEntry>,
|
||||
private val dao:HeartRateDao): DelegatedDao(changes), HeartRateDao by dao {
|
||||
|
||||
override fun insertNewEntry(entry: HeartRate): Long {
|
||||
changes.add(entry)
|
||||
return dao.insertNewEntry(entry)
|
||||
}
|
||||
|
||||
override fun updateExistingEntry(entry: HeartRate): Long {
|
||||
changes.add(entry)
|
||||
return dao.updateExistingEntry(entry)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package info.nightscout.database.impl.transactions
|
||||
|
||||
import info.nightscout.database.entities.HeartRate
|
||||
|
||||
class InsertOrUpdateHeartRateTransaction(private val heartRate: HeartRate):
|
||||
Transaction<InsertOrUpdateHeartRateTransaction.TransactionResult>() {
|
||||
|
||||
override fun run(): TransactionResult {
|
||||
val existing = if (heartRate.id == 0L) null else database.heartRateDao.findById(heartRate.id)
|
||||
return if (existing == null) {
|
||||
database.heartRateDao.insertNewEntry(heartRate).let { id ->
|
||||
TransactionResult(listOf(heartRate), emptyList()) }
|
||||
} else {
|
||||
database.heartRateDao.updateExistingEntry(heartRate)
|
||||
TransactionResult(emptyList(), listOf(heartRate))
|
||||
}
|
||||
}
|
||||
|
||||
data class TransactionResult(val inserted: List<HeartRate>, val updated: List<HeartRate>)
|
||||
}
|
Loading…
Reference in a new issue