diff --git a/app/build.gradle b/app/build.gradle
index 04fb0b520d..8d71d287b1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -235,6 +235,7 @@ dependencies {
wearApp project(':wear')
implementation project(':core')
+ implementation project(':database')
implementation project(':dana')
implementation project(':danars')
implementation project(':danar')
diff --git a/build.gradle b/build.gradle
index 977c6fe4ce..6f9baba6ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,12 +2,12 @@
buildscript {
ext {
- kotlin_version = '1.4.21'
+ kotlin_version = '1.4.30'
coreVersion = '1.3.2'
- rxjava_version = '2.2.19'
+ rxjava_version = '2.2.20'
rxandroid_version = '2.1.1'
rxkotlin_version = '2.4.0'
- room_version = '2.2.5'
+ room_version = '2.2.6'
lifecycle_version = '2.2.0'
dagger_version = '2.31.2'
coroutinesVersion = '1.3.7'
diff --git a/database/.gitignore b/database/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/database/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/database/build.gradle b/database/build.gradle
new file mode 100644
index 0000000000..5c86517f06
--- /dev/null
+++ b/database/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ }
+ firebaseDisable {
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ implementation "androidx.core:core-ktx:$coreVersion"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
+ implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
+ implementation("io.reactivex.rxjava2:rxkotlin:$rxkotlin_version")
+
+ implementation "com.google.code.gson:gson:2.8.6"
+
+ api "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
+ implementation "androidx.room:room-rxjava2:$room_version"
+
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
+
+ implementation "com.google.dagger:dagger-android:$dagger_version"
+ implementation "com.google.dagger:dagger-android-support:$dagger_version"
+ annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
+ annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
+ kapt "com.google.dagger:dagger-android-processor:$dagger_version"
+ kapt "com.google.dagger:dagger-compiler:$dagger_version"
+}
diff --git a/database/consumer-rules.pro b/database/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/database/proguard-rules.pro b/database/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/database/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/database/src/androidTest/java/info/nightscout/database/ExampleInstrumentedTest.kt b/database/src/androidTest/java/info/nightscout/database/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000000..d218cd479f
--- /dev/null
+++ b/database/src/androidTest/java/info/nightscout/database/ExampleInstrumentedTest.kt
@@ -0,0 +1,25 @@
+package info.nightscout.database
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("info.nightscout.database.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/AndroidManifest.xml b/database/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..fa796cae8a
--- /dev/null
+++ b/database/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/database/src/main/java/info/nightscout/androidaps/database/AppDatabase.kt b/database/src/main/java/info/nightscout/androidaps/database/AppDatabase.kt
new file mode 100644
index 0000000000..ffe65a8bdc
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/AppDatabase.kt
@@ -0,0 +1,55 @@
+package info.nightscout.androidaps.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import info.nightscout.androidaps.database.daos.*
+import info.nightscout.androidaps.database.entities.*
+import info.nightscout.androidaps.database.entities.APSResultLink
+import info.nightscout.androidaps.database.entities.MealLink
+import info.nightscout.androidaps.database.entities.MultiwaveBolusLink
+
+const val DATABASE_VERSION = 1
+
+@Database(version = DATABASE_VERSION, entities = arrayOf(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, MealLink::class, MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class))
+@TypeConverters(Converters::class)
+internal abstract class AppDatabase : RoomDatabase() {
+
+ abstract val glucoseValueDao: GlucoseValueDao
+
+ abstract val therapyEventDao: TherapyEventDao
+
+ abstract val temporaryBasalDao: TemporaryBasalDao
+
+ abstract val bolusDao: BolusDao
+
+ abstract val extendedBolusDao: ExtendedBolusDao
+
+ abstract val multiwaveBolusLinkDao: MultiwaveBolusLinkDao
+
+ abstract val totalDailyDoseDao: TotalDailyDoseDao
+
+ abstract val carbsDao: CarbsDao
+
+ abstract val mealLinkDao: MealLinkDao
+
+ abstract val temporaryTargetDao: TemporaryTargetDao
+
+ abstract val apsResultLinkDao: APSResultLinkDao
+
+ abstract val bolusCalculatorResultDao: BolusCalculatorResultDao
+
+ abstract val effectiveProfileSwitchDao: EffectiveProfileSwitchDao
+
+ abstract val profileSwitchDao: ProfileSwitchDao
+
+ abstract val apsResultDao: APSResultDao
+
+ abstract val versionChangeDao: VersionChangeDao
+
+ abstract val preferenceChangeDao: PreferenceChangeDao
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt b/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt
new file mode 100644
index 0000000000..0a13455b06
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt
@@ -0,0 +1,118 @@
+package info.nightscout.androidaps.database
+
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+import info.nightscout.androidaps.database.interfaces.DBEntry
+import info.nightscout.androidaps.database.transactions.Transaction
+import io.reactivex.Completable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.schedulers.Schedulers
+import io.reactivex.subjects.PublishSubject
+import java.util.concurrent.Callable
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AppRepository @Inject internal constructor(
+ internal val database: AppDatabase
+) {
+
+ private val changeSubject = PublishSubject.create>()
+
+ fun changeObservable(): Observable> = changeSubject.subscribeOn(Schedulers.io())
+
+ val databaseVersion = DATABASE_VERSION
+
+ /**
+ * Executes a transaction ignoring its result
+ * Runs on IO scheduler
+ */
+ fun runTransaction(transaction: Transaction): Completable {
+ val changes = mutableListOf()
+ return Completable.fromCallable {
+ database.runInTransaction {
+ transaction.database = DelegatedAppDatabase(changes, database)
+ transaction.run()
+ }
+ }.subscribeOn(Schedulers.io()).doOnComplete {
+ changeSubject.onNext(changes)
+ }
+ }
+
+ /**
+ * Executes a transaction and returns its result
+ * Runs on IO scheduler
+ */
+ fun runTransactionForResult(transaction: Transaction): Single {
+ val changes = mutableListOf()
+ return Single.fromCallable {
+ database.runInTransaction(Callable {
+ transaction.database = DelegatedAppDatabase(changes, database)
+ transaction.run()
+ })
+ }.subscribeOn(Schedulers.io()).doOnSuccess {
+ changeSubject.onNext(changes)
+ }
+ }
+
+ fun clearDatabases() = database.clearAllTables()
+
+ //BG READINGS -- only valid records
+ fun compatGetBgReadingsDataFromTime(timestamp: Long, ascending: Boolean) =
+ database.glucoseValueDao.compatGetBgReadingsDataFromTime(timestamp)
+ .map { if (!ascending) it.reversed() else it }
+ .subscribeOn(Schedulers.io())
+
+ fun compatGetBgReadingsDataFromTime(start: Long, end: Long, ascending: Boolean) =
+ database.glucoseValueDao.compatGetBgReadingsDataFromTime(start, end)
+ .map { if (!ascending) it.reversed() else it }
+ .subscribeOn(Schedulers.io())
+
+ //BG READINGS -- including invalid/history records
+ fun findBgReadingByNSIdSingle(nsId: String): Single> =
+ database.glucoseValueDao.findByNSIdMaybe(nsId).toWrappedSingle()
+
+ fun getModifiedBgReadingsDataFromId(lastId: Long) =
+ database.glucoseValueDao.getModifiedFrom(lastId)
+ .subscribeOn(Schedulers.io())
+
+ fun getBgReadingsCorrespondingLastHistoryRecord(lastId: Long) =
+ database.glucoseValueDao.getLastHistoryRecord(lastId)
+
+ @Suppress("unused") // debug purpose only
+ fun getAllBgReadingsStartingFrom(lastId: Long) =
+ database.glucoseValueDao.getAllStartingFrom(lastId)
+ .subscribeOn(Schedulers.io())
+
+ // TEMP TARGETS
+ fun compatGetTemporaryTargetData() =
+ database.temporaryTargetDao.compatGetTemporaryTargetData()
+
+ fun compatGetTemporaryTargetDataFromTime(timestamp: Long, ascending: Boolean) =
+ database.temporaryTargetDao.compatGetTemporaryTargetDataFromTime(timestamp)
+ .map { if (!ascending) it.reversed() else it }
+ .subscribeOn(Schedulers.io())
+
+ fun findTemporaryTargetByNSIdSingle(nsId: String): Single> =
+ database.temporaryTargetDao.findByNSIdMaybe(nsId).toWrappedSingle()
+
+ fun getModifiedTemporaryTargetsDataFromId(lastId: Long) =
+ database.temporaryTargetDao.getModifiedFrom(lastId)
+ .subscribeOn(Schedulers.io())
+
+ fun getTemporaryTargetsCorrespondingLastHistoryRecord(lastId: Long) =
+ database.temporaryTargetDao.getLastHistoryRecord(lastId)
+
+}
+
+inline fun Maybe.toWrappedSingle(): Single> =
+ this.map { ValueWrapper.Existing(it) as ValueWrapper }
+ .switchIfEmpty(Maybe.just(ValueWrapper.Absent()))
+ .toSingle()
+
+sealed class ValueWrapper {
+ data class Existing(val value: T) : ValueWrapper()
+ class Absent : ValueWrapper()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/Converters.kt b/database/src/main/java/info/nightscout/androidaps/database/Converters.kt
new file mode 100644
index 0000000000..cdac1563d5
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/Converters.kt
@@ -0,0 +1,142 @@
+package info.nightscout.androidaps.database
+
+import androidx.room.TypeConverter
+import info.nightscout.androidaps.database.data.Block
+import info.nightscout.androidaps.database.data.TargetBlock
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.*
+import org.json.JSONArray
+import org.json.JSONObject
+
+class Converters {
+
+ @TypeConverter
+ fun fromBolusType(bolusType: Bolus.Type?) = bolusType?.name
+
+ @TypeConverter
+ fun toBolusType(bolusType: String?) = bolusType?.let { Bolus.Type.valueOf(it) }
+
+ @TypeConverter
+ fun fromTrendArrow(trendArrow: GlucoseValue.TrendArrow?) = trendArrow?.name
+
+ @TypeConverter
+ fun toTrendArrow(trendArrow: String?) = trendArrow?.let { GlucoseValue.TrendArrow.valueOf(it) }
+
+ @TypeConverter
+ fun fromSourceSensor(sourceSensor: GlucoseValue.SourceSensor?) = sourceSensor?.name
+
+ @TypeConverter
+ fun toSourceSensor(sourceSensor: String?) = sourceSensor?.let { GlucoseValue.SourceSensor.valueOf(it) }
+
+ @TypeConverter
+ fun fromTBRType(tbrType: TemporaryBasal.Type?) = tbrType?.name
+
+ @TypeConverter
+ fun toTBRType(tbrType: String?) = tbrType?.let { TemporaryBasal.Type.valueOf(it) }
+
+ @TypeConverter
+ fun fromTempTargetReason(tempTargetReason: TemporaryTarget.Reason?) = tempTargetReason?.name
+
+ @TypeConverter
+ fun toTempTargetReason(tempTargetReason: String?) = tempTargetReason?.let { TemporaryTarget.Reason.valueOf(it) }
+
+ @TypeConverter
+ fun fromTherapyEventType(therapyEventType: TherapyEvent.Type?) = therapyEventType?.name
+
+ @TypeConverter
+ fun toTherapyEventType(therapyEventType: String?) = therapyEventType?.let { TherapyEvent.Type.valueOf(it) }
+
+ @TypeConverter
+ fun fromGlucoseUnit(glucoseUnit: ProfileSwitch.GlucoseUnit?) = glucoseUnit?.name
+
+ @TypeConverter
+ fun toGlucoseUnit(glucoseUnit: String?) = glucoseUnit?.let { ProfileSwitch.GlucoseUnit.valueOf(it) }
+
+ @TypeConverter
+ fun fromPumpType(pumpType: InterfaceIDs.PumpType?) = pumpType?.name
+
+ @TypeConverter
+ fun toPumpType(pumpType: String?) = pumpType?.let { InterfaceIDs.PumpType.valueOf(it) }
+
+ @TypeConverter
+ fun fromAlgorithm(algorithm: APSResult.Algorithm?) = algorithm?.name
+
+ @TypeConverter
+ fun toAlgorithm(algorithm: String?) = algorithm?.let { APSResult.Algorithm.valueOf(it) }
+
+ @TypeConverter
+ fun fromListOfBlocks(blocks: List?): String? {
+ if (blocks == null) return null
+ val jsonArray = JSONArray()
+ blocks.forEach {
+ val jsonObject = JSONObject()
+ jsonObject.put("duration", it.duration)
+ jsonObject.put("amount", it.amount)
+ jsonArray.put(jsonObject)
+ }
+ return jsonArray.toString()
+ }
+
+ @TypeConverter
+ fun toListOfBlocks(jsonString: String?): List? {
+ if (jsonString == null) return null
+ val jsonArray = JSONArray(jsonString)
+ val list = mutableListOf()
+ for (i in 0 until jsonArray.length()) {
+ val jsonObject = jsonArray.getJSONObject(i)
+ list.add(Block(jsonObject.getLong("duration"), jsonObject.getDouble("amount")))
+ }
+ return list
+ }
+
+ @TypeConverter
+ fun anyToString(value: Any?) = when (value) {
+ null -> null
+ is String -> "S$value"
+ is Int -> "I$value"
+ is Long -> "L$value"
+ is Boolean -> "B$value"
+ is Float -> "F$value"
+ else -> throw IllegalArgumentException("Type not supported")
+ }
+
+ @TypeConverter
+ fun stringToAny(value: String?): Any? = when {
+ value == null -> null
+ value.startsWith("S") -> value.substring(1)
+ value.startsWith("I") -> value.substring(1).toInt()
+ value.startsWith("L") -> value.substring(1).toLong()
+ value.startsWith("B") -> value.substring(1).toBoolean()
+ value.startsWith("F") -> value.substring(1).toFloat()
+ else -> throw IllegalArgumentException("Type not supported")
+ }
+
+ @TypeConverter
+ fun fromListOfTargetBlocks(blocks: List?): String? {
+ if (blocks == null) return null
+ val jsonArray = JSONArray()
+ blocks.forEach {
+ val jsonObject = JSONObject()
+ jsonObject.put("duration", it.duration)
+ jsonObject.put("lowTarget", it.lowTarget)
+ jsonObject.put("highTarget", it.highTarget)
+ jsonArray.put(jsonObject)
+ }
+ return jsonArray.toString()
+ }
+
+ @TypeConverter
+ fun toListOfTargetBlocks(jsonString: String?): List? {
+ if (jsonString == null) return null
+ val jsonArray = JSONArray(jsonString)
+ val list = mutableListOf()
+ for (i in 0 until jsonArray.length()) {
+ val jsonObject = jsonArray.getJSONObject(i)
+ list.add(TargetBlock(jsonObject.getLong("duration"),
+ jsonObject.getDouble("lowTarget"),
+ jsonObject.getDouble("highTarget")))
+ }
+ return list
+ }
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt b/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt
new file mode 100644
index 0000000000..dda8cf3917
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt
@@ -0,0 +1,24 @@
+package info.nightscout.androidaps.database
+
+import android.content.Context
+import androidx.room.Room
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+import javax.inject.Singleton
+
+@Module
+open class DatabaseModule {
+
+ @DbFileName
+ @Provides
+ fun dbFileName() = "androidaps.db"
+
+ @Provides
+ @Singleton
+ internal fun provideAppDatabase(context: Context, @DbFileName fileName: String) =
+ Room.databaseBuilder(context, AppDatabase::class.java, fileName).build()
+
+ @Qualifier
+ annotation class DbFileName
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/DelegatedAppDatabase.kt b/database/src/main/java/info/nightscout/androidaps/database/DelegatedAppDatabase.kt
new file mode 100644
index 0000000000..1a6e3206b0
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/DelegatedAppDatabase.kt
@@ -0,0 +1,27 @@
+package info.nightscout.androidaps.database
+
+import info.nightscout.androidaps.database.daos.*
+import info.nightscout.androidaps.database.daos.delegated.*
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedAppDatabase(val changes: MutableList, val database: AppDatabase) {
+
+ val glucoseValueDao: GlucoseValueDao = DelegatedGlucoseValueDao(changes, database.glucoseValueDao)
+ val therapyEventDao: TherapyEventDao = DelegatedTherapyEventDao(changes, database.therapyEventDao)
+ val temporaryBasalDao: TemporaryBasalDao = DelegatedTemporaryBasalDao(changes, database.temporaryBasalDao)
+ val bolusDao: BolusDao = DelegatedBolusDao(changes, database.bolusDao)
+ val extendedBolusDao: ExtendedBolusDao = DelegatedExtendedExtendedBolusDao(changes, database.extendedBolusDao)
+ val multiwaveBolusLinkDao: MultiwaveBolusLinkDao = DelegatedMultiwaveBolusLinkDao(changes, database.multiwaveBolusLinkDao)
+ val totalDailyDoseDao: TotalDailyDoseDao = DelegatedTotalDailyDoseDao(changes, database.totalDailyDoseDao)
+ val carbsDao: CarbsDao = DelegatedCarbsDao(changes, database.carbsDao)
+ val mealLinkDao: MealLinkDao = DelegatedMealLinkDao(changes, database.mealLinkDao)
+ val temporaryTargetDao: TemporaryTargetDao = DelegatedTemporaryTargetDao(changes, database.temporaryTargetDao)
+ val apsResultLinkDao: APSResultLinkDao = DelegatedAPSResultLinkLinkDao(changes, database.apsResultLinkDao)
+ val bolusCalculatorResultDao: BolusCalculatorResultDao = DelegatedBolusCalculatorResultDao(changes, database.bolusCalculatorResultDao)
+ val effectiveProfileSwitchDao: EffectiveProfileSwitchDao = DelegatedEffectiveProfileSwitchDao(changes, database.effectiveProfileSwitchDao)
+ val profileSwitchDao: ProfileSwitchDao = DelegatedProfileSwitchDao(changes, database.profileSwitchDao)
+ val apsResultDao: APSResultDao = DelegatedAPSResultDao(changes, database.apsResultDao)
+ val versionChangeDao: VersionChangeDao = DelegatedVersionChangeDao(changes, database.versionChangeDao)
+ val preferenceChangeDao: PreferenceChangeDao = DelegatedPreferenceChangeDao(changes, database.preferenceChangeDao)
+ fun clearAllTables() = database.clearAllTables()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/TableNames.kt b/database/src/main/java/info/nightscout/androidaps/database/TableNames.kt
new file mode 100644
index 0000000000..8ecb6beeff
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/TableNames.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.database
+
+const val TABLE_APS_RESULTS = "apsResults"
+const val TABLE_APS_RESULT_LINKS = "apsResultLinks"
+const val TABLE_BOLUSES = "boluses"
+const val TABLE_BOLUS_CALCULATOR_RESULTS = "bolusCalculatorResults"
+const val TABLE_CARBS = "carbs"
+const val TABLE_EFFECTIVE_PROFILE_SWITCHES = "effectiveProfileSwitches"
+const val TABLE_EXTENDED_BOLUSES = "extendedBoluses"
+const val TABLE_GLUCOSE_VALUES = "glucoseValues"
+const val TABLE_MEAL_LINKS = "mealLinks"
+const val TABLE_MULTIWAVE_BOLUS_LINKS = "multiwaveBolusLinks"
+const val TABLE_PROFILE_SWITCHES = "profileSwitches"
+const val TABLE_TEMPORARY_BASALS = "temporaryBasals"
+const val TABLE_TEMPORARY_TARGETS = "temporaryTargets"
+const val TABLE_TOTAL_DAILY_DOSES = "totalDailyDoses"
+const val TABLE_THERAPY_EVENTS = "therapyEvents"
+const val TABLE_PREFERENCE_CHANGES = "preferenceChanges"
+const val TABLE_VERSION_CHANGES = "versionChanges"
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultDao.kt
new file mode 100644
index 0000000000..62a1608cdb
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_APS_RESULTS
+import info.nightscout.androidaps.database.entities.APSResult
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface APSResultDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_APS_RESULTS WHERE id = :id")
+ override fun findById(id: Long): APSResult?
+
+ @Query("DELETE FROM $TABLE_APS_RESULTS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultLinkDao.kt
new file mode 100644
index 0000000000..e19a605897
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/APSResultLinkDao.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_APS_RESULTS
+import info.nightscout.androidaps.database.TABLE_APS_RESULT_LINKS
+import info.nightscout.androidaps.database.entities.APSResultLink
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface APSResultLinkDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_APS_RESULT_LINKS WHERE id = :id")
+ override fun findById(id: Long): APSResultLink?
+
+ @Query("DELETE FROM $TABLE_APS_RESULTS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/BolusCalculatorResultDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/BolusCalculatorResultDao.kt
new file mode 100644
index 0000000000..a4602a7d73
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/BolusCalculatorResultDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_BOLUS_CALCULATOR_RESULTS
+import info.nightscout.androidaps.database.entities.BolusCalculatorResult
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface BolusCalculatorResultDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_BOLUS_CALCULATOR_RESULTS WHERE id = :id")
+ override fun findById(id: Long): BolusCalculatorResult?
+
+ @Query("DELETE FROM $TABLE_BOLUS_CALCULATOR_RESULTS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/BolusDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/BolusDao.kt
new file mode 100644
index 0000000000..18e0ae0121
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/BolusDao.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_BOLUSES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.Bolus
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface BolusDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_BOLUSES WHERE id = :id")
+ override fun findById(id: Long): Bolus?
+
+ @Query("DELETE FROM $TABLE_BOLUSES")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/CarbsDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/CarbsDao.kt
new file mode 100644
index 0000000000..09d24908d0
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/CarbsDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_CARBS
+import info.nightscout.androidaps.database.entities.Carbs
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface CarbsDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_CARBS WHERE id = :id")
+ override fun findById(id: Long): Carbs?
+
+ @Query("DELETE FROM $TABLE_CARBS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/EffectiveProfileSwitchDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/EffectiveProfileSwitchDao.kt
new file mode 100644
index 0000000000..8419b30e3e
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/EffectiveProfileSwitchDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_EFFECTIVE_PROFILE_SWITCHES
+import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface EffectiveProfileSwitchDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_EFFECTIVE_PROFILE_SWITCHES WHERE id = :id")
+ override fun findById(id: Long): EffectiveProfileSwitch?
+
+ @Query("DELETE FROM $TABLE_EFFECTIVE_PROFILE_SWITCHES")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/ExtendedBolusDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/ExtendedBolusDao.kt
new file mode 100644
index 0000000000..c5e0b979bd
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/ExtendedBolusDao.kt
@@ -0,0 +1,20 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_EXTENDED_BOLUSES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.ExtendedBolus
+import io.reactivex.Flowable
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface ExtendedBolusDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_EXTENDED_BOLUSES WHERE id = :id")
+ override fun findById(id: Long): ExtendedBolus?
+
+ @Query("DELETE FROM $TABLE_EXTENDED_BOLUSES")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt
new file mode 100644
index 0000000000..fdaefe78ad
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt
@@ -0,0 +1,42 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_GLUCOSE_VALUES
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import io.reactivex.Maybe
+import io.reactivex.Single
+
+@Dao
+internal interface GlucoseValueDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE id = :id")
+ override fun findById(id: Long): GlucoseValue?
+
+ @Query("DELETE FROM $TABLE_GLUCOSE_VALUES")
+ override fun deleteAllEntries()
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE nightscoutId = :nsId AND referenceId IS NULL")
+ fun findByNSIdMaybe(nsId: String): Maybe
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE timestamp = :timestamp AND sourceSensor = :sourceSensor AND referenceId IS NULL")
+ fun findByTimestampAndSensor(timestamp: Long, sourceSensor: GlucoseValue.SourceSensor): GlucoseValue?
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE timestamp >= :timestamp AND isValid = 1 AND referenceId IS NULL AND value >= 39 ORDER BY timestamp ASC")
+ fun compatGetBgReadingsDataFromTime(timestamp: Long): Single>
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE timestamp BETWEEN :start AND :end AND isValid = 1 AND referenceId IS NULL AND value >= 39 ORDER BY timestamp ASC")
+ fun compatGetBgReadingsDataFromTime(start: Long, end: Long): Single>
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE id > :lastId AND referenceId IS NULL ORDER BY timestamp ASC")
+ fun getDataFromId(lastId: Long): Single>
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE id >= :id")
+ fun getAllStartingFrom(id: Long): Single>
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE referenceId = :id ORDER BY id DESC LIMIT 1")
+ fun getLastHistoryRecord(id: Long): GlucoseValue?
+
+ @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE id > :id AND referenceId IS NULL OR id IN (SELECT DISTINCT referenceId FROM $TABLE_GLUCOSE_VALUES WHERE id > :id) ORDER BY id ASC")
+ fun getModifiedFrom(id: Long): Single>
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/MealLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/MealLinkDao.kt
new file mode 100644
index 0000000000..86c6904e30
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/MealLinkDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_MEAL_LINKS
+import info.nightscout.androidaps.database.entities.MealLink
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface MealLinkDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_MEAL_LINKS WHERE id = :id")
+ override fun findById(id: Long): MealLink?
+
+ @Query("DELETE FROM $TABLE_MEAL_LINKS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/MultiwaveBolusLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/MultiwaveBolusLinkDao.kt
new file mode 100644
index 0000000000..d7865779b7
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/MultiwaveBolusLinkDao.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_MEAL_LINKS
+import info.nightscout.androidaps.database.TABLE_MULTIWAVE_BOLUS_LINKS
+import info.nightscout.androidaps.database.entities.MultiwaveBolusLink
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface MultiwaveBolusLinkDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_MULTIWAVE_BOLUS_LINKS WHERE id = :id")
+ override fun findById(id: Long): MultiwaveBolusLink?
+
+ @Query("DELETE FROM $TABLE_MEAL_LINKS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/PreferenceChangeDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/PreferenceChangeDao.kt
new file mode 100644
index 0000000000..49388a6d37
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/PreferenceChangeDao.kt
@@ -0,0 +1,16 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_PREFERENCE_CHANGES
+import info.nightscout.androidaps.database.entities.PreferenceChange
+import io.reactivex.Single
+
+@Dao
+interface PreferenceChangeDao {
+
+ @Insert
+ fun insert(preferenceChange: PreferenceChange)
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/ProfileSwitchDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/ProfileSwitchDao.kt
new file mode 100644
index 0000000000..574a9353d8
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/ProfileSwitchDao.kt
@@ -0,0 +1,37 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_PROFILE_SWITCHES
+import info.nightscout.androidaps.database.data.checkSanity
+import info.nightscout.androidaps.database.daos.workaround.ProfileSwitchDaoWorkaround
+import info.nightscout.androidaps.database.entities.ProfileSwitch
+import io.reactivex.Flowable
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface ProfileSwitchDao : ProfileSwitchDaoWorkaround {
+
+ @Query("SELECT * FROM $TABLE_PROFILE_SWITCHES WHERE id = :id")
+ override fun findById(id: Long): ProfileSwitch?
+
+ @Query("DELETE FROM $TABLE_PROFILE_SWITCHES")
+ override fun deleteAllEntries()
+}
+
+internal fun ProfileSwitchDao.insertNewEntryImpl(entry: ProfileSwitch): Long {
+ if (!entry.basalBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for basal blocks.")
+ if (!entry.icBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for IC blocks.")
+ if (!entry.isfBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for ISF blocks.")
+ if (!entry.targetBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for target blocks.")
+ return (this as TraceableDao).insertNewEntryImpl(entry)
+}
+
+internal fun ProfileSwitchDao.updateExistingEntryImpl(entry: ProfileSwitch): Long {
+ if (!entry.basalBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for basal blocks.")
+ if (!entry.icBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for IC blocks.")
+ if (!entry.isfBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for ISF blocks.")
+ if (!entry.targetBlocks.checkSanity()) throw IllegalArgumentException("Sanity check failed for target blocks.")
+ return (this as TraceableDao).updateExistingEntryImpl(entry)
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryBasalDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryBasalDao.kt
new file mode 100644
index 0000000000..b2b88a1c7a
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryBasalDao.kt
@@ -0,0 +1,21 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_TEMPORARY_BASALS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.TemporaryBasal
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface TemporaryBasalDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_BASALS WHERE id = :id")
+ override fun findById(id: Long): TemporaryBasal?
+
+ @Query("DELETE FROM $TABLE_TEMPORARY_BASALS")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryTargetDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryTargetDao.kt
new file mode 100644
index 0000000000..bab28cfbc1
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/TemporaryTargetDao.kt
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_TEMPORARY_TARGETS
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface TemporaryTargetDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE id = :id")
+ override fun findById(id: Long): TemporaryTarget?
+
+ @Query("DELETE FROM $TABLE_TEMPORARY_TARGETS")
+ override fun deleteAllEntries()
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE nightscoutId = :nsId AND referenceId IS NULL")
+ fun findByNSIdMaybe(nsId: String): Maybe
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE timestamp <= :timestamp AND (timestamp + duration) > :timestamp AND referenceId IS NULL AND isValid = 1 ORDER BY timestamp DESC LIMIT 1")
+ fun getTemporaryTargetActiveAt(timestamp: Long): TemporaryTarget?
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE timestamp >= :timestamp AND isValid = 1 AND referenceId IS NULL ORDER BY timestamp ASC")
+ fun compatGetTemporaryTargetDataFromTime(timestamp: Long): Single>
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE isValid = 1 AND referenceId IS NULL ORDER BY timestamp ASC")
+ fun compatGetTemporaryTargetData(): List
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE referenceId = :id ORDER BY id DESC LIMIT 1")
+ fun getLastHistoryRecord(id: Long): TemporaryTarget?
+
+ @Query("SELECT * FROM $TABLE_TEMPORARY_TARGETS WHERE id > :id AND referenceId IS NULL OR id IN (SELECT DISTINCT referenceId FROM $TABLE_TEMPORARY_TARGETS WHERE id > :id) ORDER BY id ASC")
+ fun getModifiedFrom(id: Long): Single>
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/TherapyEventDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/TherapyEventDao.kt
new file mode 100644
index 0000000000..627b896a7c
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/TherapyEventDao.kt
@@ -0,0 +1,24 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_THERAPY_EVENTS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.TherapyEvent
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Single
+
+@Dao
+internal interface TherapyEventDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_THERAPY_EVENTS WHERE id = :id")
+ override fun findById(id: Long): TherapyEvent?
+
+ @Query("DELETE FROM $TABLE_THERAPY_EVENTS")
+ override fun deleteAllEntries()
+
+ @Query("SELECT * FROM $TABLE_THERAPY_EVENTS WHERE type = :type AND timestamp = :timestamp AND referenceId IS NULL")
+ fun findByTimestamp(type: TherapyEvent.Type, timestamp: Long): TherapyEvent?
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/TotalDailyDoseDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/TotalDailyDoseDao.kt
new file mode 100644
index 0000000000..16ad4e0253
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/TotalDailyDoseDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_TOTAL_DAILY_DOSES
+import info.nightscout.androidaps.database.entities.TotalDailyDose
+import io.reactivex.Single
+
+@Suppress("FunctionName")
+@Dao
+internal interface TotalDailyDoseDao : TraceableDao {
+
+ @Query("SELECT * FROM $TABLE_TOTAL_DAILY_DOSES WHERE id = :id")
+ override fun findById(id: Long): TotalDailyDose?
+
+ @Query("DELETE FROM $TABLE_TOTAL_DAILY_DOSES")
+ override fun deleteAllEntries()
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/TraceableDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/TraceableDao.kt
new file mode 100644
index 0000000000..7eed5e5528
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/TraceableDao.kt
@@ -0,0 +1,60 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Insert
+import androidx.room.Update
+import info.nightscout.androidaps.database.daos.workaround.TraceableDaoWorkaround
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import io.reactivex.Single
+
+internal interface TraceableDao : TraceableDaoWorkaround {
+
+ fun findById(id: Long): T?
+
+ fun deleteAllEntries()
+
+ //fun getAllStartingFrom(id: Long): Single>
+
+ @Insert
+ fun insert(entry: T): Long
+
+ @Update
+ fun update(entry: T)
+}
+
+/**
+ * Inserts a new entry
+ * @return The ID of the newly generated entry
+ */
+//@Transaction
+internal fun TraceableDao.insertNewEntryImpl(entry: T): Long {
+ if (entry.id != 0L) throw IllegalArgumentException("ID must be 0.")
+ if (entry.version != 0) throw IllegalArgumentException("Version must be 0.")
+ if (entry.referenceId != null) throw IllegalArgumentException("Reference ID must be null.")
+ if (!entry.foreignKeysValid) throw IllegalArgumentException("One or more foreign keys are invalid (e.g. 0 value).")
+ val lastModified = System.currentTimeMillis()
+ entry.dateCreated = lastModified
+ val id = insert(entry)
+ entry.id = id
+ return id
+}
+
+/**
+ * Updates an existing entry
+ * @return The ID of the newly generated HISTORIC entry
+ */
+//@Transaction
+internal fun TraceableDao.updateExistingEntryImpl(entry: T): Long {
+ if (entry.id == 0L) throw IllegalArgumentException("ID must not be 0.")
+ if (entry.referenceId != null) throw IllegalArgumentException("Reference ID must be null.")
+ if (!entry.foreignKeysValid) throw IllegalArgumentException("One or more foreign keys are invalid (e.g. 0 value).")
+ val lastModified = System.currentTimeMillis()
+ entry.dateCreated = lastModified
+ val current = findById(entry.id)
+ ?: throw IllegalArgumentException("The entry with the specified ID does not exist.")
+ if (current.referenceId != null) throw IllegalArgumentException("The entry with the specified ID is historic and cannot be updated.")
+ entry.version = current.version + 1
+ update(entry)
+ current.referenceId = entry.id
+ current.id = 0
+ return insert(current)
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/VersionChangeDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/VersionChangeDao.kt
new file mode 100644
index 0000000000..fabbead4fa
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/VersionChangeDao.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.database.daos
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import info.nightscout.androidaps.database.TABLE_VERSION_CHANGES
+import info.nightscout.androidaps.database.entities.VersionChange
+import io.reactivex.Single
+
+@Dao
+interface VersionChangeDao {
+
+ @Insert
+ fun insert(versionChange: VersionChange)
+
+ @Query("SELECT * FROM $TABLE_VERSION_CHANGES ORDER BY id DESC LIMIT 1")
+ fun getMostRecentVersionChange(): VersionChange?
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultDao.kt
new file mode 100644
index 0000000000..ffe6180770
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.APSResultDao
+import info.nightscout.androidaps.database.entities.APSResult
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedAPSResultDao(changes: MutableList, private val dao: APSResultDao) : DelegatedDao(changes), APSResultDao by dao {
+
+ override fun insertNewEntry(entry: APSResult): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: APSResult): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultLinkDao.kt
new file mode 100644
index 0000000000..2152d3f9f0
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedAPSResultLinkDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.APSResultLinkDao
+import info.nightscout.androidaps.database.entities.APSResultLink
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedAPSResultLinkLinkDao(changes: MutableList, private val dao: APSResultLinkDao) : DelegatedDao(changes), APSResultLinkDao by dao {
+
+ override fun insertNewEntry(entry: APSResultLink): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: APSResultLink): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusCalculatorResultDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusCalculatorResultDao.kt
new file mode 100644
index 0000000000..40e92dcb04
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusCalculatorResultDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.BolusCalculatorResultDao
+import info.nightscout.androidaps.database.entities.BolusCalculatorResult
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedBolusCalculatorResultDao(changes: MutableList, private val dao: BolusCalculatorResultDao) : DelegatedDao(changes), BolusCalculatorResultDao by dao {
+
+ override fun insertNewEntry(entry: BolusCalculatorResult): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: BolusCalculatorResult): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusDao.kt
new file mode 100644
index 0000000000..67afec9e18
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedBolusDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.BolusDao
+import info.nightscout.androidaps.database.entities.Bolus
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedBolusDao(changes: MutableList, private val dao: BolusDao) : DelegatedDao(changes), BolusDao by dao {
+
+ override fun insertNewEntry(entry: Bolus): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: Bolus): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedCarbsDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedCarbsDao.kt
new file mode 100644
index 0000000000..0a2da34a05
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedCarbsDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.CarbsDao
+import info.nightscout.androidaps.database.entities.Carbs
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedCarbsDao(changes: MutableList, private val dao: CarbsDao) : DelegatedDao(changes), CarbsDao by dao {
+
+ override fun insertNewEntry(entry: Carbs): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: Carbs): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedDao.kt
new file mode 100644
index 0000000000..ff5fe9ba64
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedDao.kt
@@ -0,0 +1,8 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+/**
+ * A DAO that adds updated or inserted entries to a list
+ */
+internal abstract class DelegatedDao(protected val changes: MutableList)
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedEffectiveProfileSwitchDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedEffectiveProfileSwitchDao.kt
new file mode 100644
index 0000000000..6516ffbc6c
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedEffectiveProfileSwitchDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.EffectiveProfileSwitchDao
+import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedEffectiveProfileSwitchDao(changes: MutableList, private val dao: EffectiveProfileSwitchDao) : DelegatedDao(changes), EffectiveProfileSwitchDao by dao {
+
+ override fun insertNewEntry(entry: EffectiveProfileSwitch): Long {
+ changes.add(entry)
+ return super.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: EffectiveProfileSwitch): Long {
+ changes.add(entry)
+ return super.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedExtendedBolusDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedExtendedBolusDao.kt
new file mode 100644
index 0000000000..fa3a6369fd
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedExtendedBolusDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.ExtendedBolusDao
+import info.nightscout.androidaps.database.entities.ExtendedBolus
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedExtendedExtendedBolusDao(changes: MutableList, private val dao: ExtendedBolusDao) : DelegatedDao(changes), ExtendedBolusDao by dao {
+
+ override fun insertNewEntry(entry: ExtendedBolus): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: ExtendedBolus): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedGlucoseValueDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedGlucoseValueDao.kt
new file mode 100644
index 0000000000..d16e84ebc5
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedGlucoseValueDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.GlucoseValueDao
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedGlucoseValueDao(changes: MutableList, private val dao: GlucoseValueDao) : DelegatedDao(changes), GlucoseValueDao by dao {
+
+ override fun insertNewEntry(entry: GlucoseValue): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: GlucoseValue): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMealLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMealLinkDao.kt
new file mode 100644
index 0000000000..a568dd5aed
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMealLinkDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.MealLinkDao
+import info.nightscout.androidaps.database.entities.MealLink
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedMealLinkDao(changes: MutableList, private val dao: MealLinkDao) : DelegatedDao(changes), MealLinkDao by dao {
+
+ override fun insertNewEntry(entry: MealLink): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: MealLink): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMultiwaveBolusLinkDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMultiwaveBolusLinkDao.kt
new file mode 100644
index 0000000000..fd6079a146
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedMultiwaveBolusLinkDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.MultiwaveBolusLinkDao
+import info.nightscout.androidaps.database.entities.MultiwaveBolusLink
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedMultiwaveBolusLinkDao(changes: MutableList, private val dao: MultiwaveBolusLinkDao) : DelegatedDao(changes), MultiwaveBolusLinkDao by dao {
+
+ override fun insertNewEntry(entry: MultiwaveBolusLink): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: MultiwaveBolusLink): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedPreferenceChangeDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedPreferenceChangeDao.kt
new file mode 100644
index 0000000000..97ba761423
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedPreferenceChangeDao.kt
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.PreferenceChangeDao
+import info.nightscout.androidaps.database.entities.PreferenceChange
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedPreferenceChangeDao(changes: MutableList, private val dao: PreferenceChangeDao) : DelegatedDao(changes), PreferenceChangeDao by dao {
+
+ override fun insert(preferenceChange: PreferenceChange) {
+ changes.add(preferenceChange)
+ return dao.insert(preferenceChange)
+ }
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedProfileSwitchDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedProfileSwitchDao.kt
new file mode 100644
index 0000000000..11ded9ccdc
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedProfileSwitchDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.ProfileSwitchDao
+import info.nightscout.androidaps.database.entities.ProfileSwitch
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedProfileSwitchDao(changes: MutableList, private val dao: ProfileSwitchDao) : DelegatedDao(changes), ProfileSwitchDao by dao {
+
+ override fun insertNewEntry(entry: ProfileSwitch): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: ProfileSwitch): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryBasalDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryBasalDao.kt
new file mode 100644
index 0000000000..c48ca004cb
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryBasalDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.TemporaryBasalDao
+import info.nightscout.androidaps.database.entities.TemporaryBasal
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedTemporaryBasalDao(changes: MutableList, private val dao: TemporaryBasalDao) : DelegatedDao(changes), TemporaryBasalDao by dao {
+
+ override fun insertNewEntry(entry: TemporaryBasal): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: TemporaryBasal): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryTargetDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryTargetDao.kt
new file mode 100644
index 0000000000..a3cba800f5
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTemporaryTargetDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.TemporaryTargetDao
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedTemporaryTargetDao(changes: MutableList, private val dao: TemporaryTargetDao) : DelegatedDao(changes), TemporaryTargetDao by dao {
+
+ override fun insertNewEntry(entry: TemporaryTarget): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: TemporaryTarget): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTherapyEventDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTherapyEventDao.kt
new file mode 100644
index 0000000000..767edd50a9
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTherapyEventDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.TherapyEventDao
+import info.nightscout.androidaps.database.entities.TherapyEvent
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedTherapyEventDao(changes: MutableList, private val dao: TherapyEventDao) : DelegatedDao(changes), TherapyEventDao by dao {
+
+ override fun insertNewEntry(entry: TherapyEvent): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: TherapyEvent): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTotalDailyDoseDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTotalDailyDoseDao.kt
new file mode 100644
index 0000000000..95bc5ab108
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedTotalDailyDoseDao.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.TotalDailyDoseDao
+import info.nightscout.androidaps.database.entities.TotalDailyDose
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedTotalDailyDoseDao(changes: MutableList, private val dao: TotalDailyDoseDao) : DelegatedDao(changes), TotalDailyDoseDao by dao {
+
+ override fun insertNewEntry(entry: TotalDailyDose): Long {
+ changes.add(entry)
+ return dao.insertNewEntry(entry)
+ }
+
+ override fun updateExistingEntry(entry: TotalDailyDose): Long {
+ changes.add(entry)
+ return dao.updateExistingEntry(entry)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedVersionChangeDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedVersionChangeDao.kt
new file mode 100644
index 0000000000..81a5ecb799
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/delegated/DelegatedVersionChangeDao.kt
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.database.daos.delegated
+
+import info.nightscout.androidaps.database.daos.VersionChangeDao
+import info.nightscout.androidaps.database.entities.VersionChange
+import info.nightscout.androidaps.database.interfaces.DBEntry
+
+internal class DelegatedVersionChangeDao(changes: MutableList, private val dao: VersionChangeDao) : DelegatedDao(changes), VersionChangeDao by dao {
+
+ override fun insert(versionChange: VersionChange) {
+ changes.add(versionChange)
+ return dao.insert(versionChange)
+ }
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/ProfileSwitchDaoWorkaround.java b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/ProfileSwitchDaoWorkaround.java
new file mode 100644
index 0000000000..9a817ff215
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/ProfileSwitchDaoWorkaround.java
@@ -0,0 +1,23 @@
+package info.nightscout.androidaps.database.daos.workaround;
+
+import androidx.room.Transaction;
+
+import info.nightscout.androidaps.database.daos.ProfileSwitchDao;
+import info.nightscout.androidaps.database.daos.ProfileSwitchDaoKt;
+import info.nightscout.androidaps.database.daos.TraceableDao;
+import info.nightscout.androidaps.database.entities.ProfileSwitch;
+
+public interface ProfileSwitchDaoWorkaround extends TraceableDao {
+
+ @Override
+ @Transaction
+ default long insertNewEntry(ProfileSwitch entry) {
+ return ProfileSwitchDaoKt.insertNewEntryImpl((ProfileSwitchDao) this, entry);
+ }
+
+ @Override
+ @Transaction
+ default long updateExistingEntry(ProfileSwitch entry) {
+ return ProfileSwitchDaoKt.updateExistingEntryImpl((ProfileSwitchDao) this, entry);
+ }
+}
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/TraceableDaoWorkaround.java b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/TraceableDaoWorkaround.java
new file mode 100644
index 0000000000..ddeb1d0d80
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/TraceableDaoWorkaround.java
@@ -0,0 +1,31 @@
+package info.nightscout.androidaps.database.daos.workaround;
+
+import androidx.room.Transaction;
+
+import info.nightscout.androidaps.database.daos.TraceableDao;
+import info.nightscout.androidaps.database.daos.TraceableDaoKt;
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry;
+
+public interface TraceableDaoWorkaround {
+
+ /**
+ * Inserts a new entry
+ *
+ * @return The ID of the newly generated entry
+ */
+ @Transaction
+ default long insertNewEntry(T entry) {
+ return TraceableDaoKt.insertNewEntryImpl((TraceableDao) this, entry);
+ }
+
+ /**
+ * Updates an existing entry
+ *
+ * @return The ID of the newly generated HISTORIC entry
+ */
+ @Transaction
+ default long updateExistingEntry(T entry) {
+ return TraceableDaoKt.updateExistingEntryImpl((TraceableDao) this, entry);
+ }
+
+}
diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/package-info.java b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/package-info.java
new file mode 100644
index 0000000000..089581be3b
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/daos/workaround/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Workarounds until Kotlin is able to properly translate interface default methods while annotation processing.
+ * See https://youtrack.jetbrains.com/issue/KT-25960
+ */
+package info.nightscout.androidaps.database.daos.workaround;
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/data/Block.kt b/database/src/main/java/info/nightscout/androidaps/database/data/Block.kt
new file mode 100644
index 0000000000..0b4a98b5c1
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/data/Block.kt
@@ -0,0 +1,11 @@
+package info.nightscout.androidaps.database.data
+
+import java.util.concurrent.TimeUnit
+
+data class Block(var duration: Long, var amount: Double)
+
+fun List.checkSanity(): Boolean {
+ var sum = 0L
+ forEach { sum += it.duration }
+ return sum == TimeUnit.DAYS.toMillis(1)
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/data/TargetBlock.kt b/database/src/main/java/info/nightscout/androidaps/database/data/TargetBlock.kt
new file mode 100644
index 0000000000..4c95fba75f
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/data/TargetBlock.kt
@@ -0,0 +1,11 @@
+package info.nightscout.androidaps.database.data
+
+import java.util.concurrent.TimeUnit
+
+data class TargetBlock(var duration: Long, var lowTarget: Double, var highTarget: Double)
+
+fun List.checkSanity(): Boolean {
+ var sum = 0L
+ forEach { sum += it.duration }
+ return sum == TimeUnit.DAYS.toMillis(1)
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/embedments/InsulinConfiguration.kt b/database/src/main/java/info/nightscout/androidaps/database/embedments/InsulinConfiguration.kt
new file mode 100644
index 0000000000..50450ae52d
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/embedments/InsulinConfiguration.kt
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.database.embedments
+
+data class InsulinConfiguration(
+ var insulinLabel: String,
+ var insulinEndTime: Long,
+ var peak: Long
+)
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt
new file mode 100644
index 0000000000..86873d9915
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt
@@ -0,0 +1,20 @@
+package info.nightscout.androidaps.database.embedments
+
+data class InterfaceIDs(
+ var nightscoutSystemId: String? = null,
+ var nightscoutId: String? = null,
+ var pumpType: PumpType? = null,
+ var pumpSerial: String? = null,
+ var pumpId: Long? = null,
+ var startId: Long? = null,
+ var endId: Long? = null
+) {
+ enum class PumpType {
+ ACCU_CHEK_INSIGHT,
+ ACCU_CHEK_COMBO,
+ DANA_R,
+ DANA_RS,
+ MEDTRONIC,
+ OMNIPOD
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/APSResult.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/APSResult.kt
new file mode 100644
index 0000000000..42ef8e7dac
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/APSResult.kt
@@ -0,0 +1,42 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_APS_RESULTS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_APS_RESULTS,
+ foreignKeys = [ForeignKey(
+ entity = APSResult::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class APSResult(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var algorithm: Algorithm,
+ var glucoseStatusJson: String,
+ var currentTempJson: String,
+ var iobDataJson: String,
+ var profileJson: String,
+ var autosensDataJson: String?,
+ var mealDataJson: String,
+ var isMicroBolusAllowed: Boolean?,
+ var resultJson: String
+) : TraceableDBEntry, DBEntryWithTime {
+ enum class Algorithm {
+ MA,
+ AMA,
+ SMB
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/APSResultLink.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/APSResultLink.kt
new file mode 100644
index 0000000000..8fed1f9578
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/APSResultLink.kt
@@ -0,0 +1,42 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_APS_RESULT_LINKS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+
+@Entity(tableName = TABLE_APS_RESULT_LINKS,
+ foreignKeys = [ForeignKey(
+ entity = APSResult::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("apsResultId")), ForeignKey(
+
+ entity = Bolus::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("smbId")), ForeignKey(
+
+ entity = TemporaryBasal::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("tbrId")), ForeignKey(
+
+ entity = APSResultLink::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("referenceId"))],
+ indices = [Index("referenceId"), Index("apsResultId"),
+ Index("smbId"), Index("tbrId")])
+data class APSResultLink(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ var apsResultId: Long,
+ var smbId: Long? = null,
+ var tbrId: Long? = null
+) : TraceableDBEntry {
+ override val foreignKeysValid: Boolean
+ get() = super.foreignKeysValid && apsResultId != 0L && smbId != 0L && tbrId != 0L
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/Bolus.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/Bolus.kt
new file mode 100644
index 0000000000..a683ca31f3
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/Bolus.kt
@@ -0,0 +1,39 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_BOLUSES
+import info.nightscout.androidaps.database.embedments.InsulinConfiguration
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_BOLUSES,
+ foreignKeys = [ForeignKey(
+ entity = Bolus::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class Bolus(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var amount: Double,
+ var type: Type,
+ var isBasalInsulin: Boolean,
+ @Embedded
+ var insulinConfiguration: InsulinConfiguration? = null
+) : TraceableDBEntry, DBEntryWithTime {
+ enum class Type {
+ NORMAL,
+ SMB,
+ PRIMING
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/BolusCalculatorResult.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/BolusCalculatorResult.kt
new file mode 100644
index 0000000000..056d1fd363
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/BolusCalculatorResult.kt
@@ -0,0 +1,53 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_BOLUS_CALCULATOR_RESULTS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_BOLUS_CALCULATOR_RESULTS,
+ foreignKeys = [ForeignKey(
+ entity = BolusCalculatorResult::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class BolusCalculatorResult(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var targetBGLow: Double,
+ var targetBGHigh: Double,
+ var isf: Double,
+ var ic: Double,
+ var bolusIOB: Double,
+ var wasBolusIOBUsed: Boolean,
+ var basalIOB: Double,
+ var wasBasalIOBUsed: Boolean,
+ var glucoseValue: Double,
+ var wasGlucoseUsed: Boolean,
+ var glucoseDifference: Double,
+ var glucoseInsulin: Double,
+ var glucoseTrend: Double,
+ var wasTrendUsed: Boolean,
+ var trendInsulin: Double,
+ var cob: Double,
+ var wasCOBUsed: Boolean,
+ var cobInsulin: Double,
+ var carbs: Double,
+ var wereCarbsUsed: Boolean,
+ var carbsInsulin: Double,
+ var otherCorrection: Double,
+ var wasSuperbolusUsed: Boolean,
+ var superbolusInsulin: Double,
+ var wasTempTargetUsed: Boolean,
+ var totalInsulin: Double
+) : TraceableDBEntry, DBEntryWithTime
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/Carbs.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/Carbs.kt
new file mode 100644
index 0000000000..5694d1041a
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/Carbs.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_CARBS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_CARBS,
+ foreignKeys = [ForeignKey(
+ entity = Carbs::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class Carbs(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ override var duration: Long,
+ var amount: Double
+) : TraceableDBEntry, DBEntryWithTimeAndDuration
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/EffectiveProfileSwitch.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/EffectiveProfileSwitch.kt
new file mode 100644
index 0000000000..273634e25a
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/EffectiveProfileSwitch.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.data.Block
+import info.nightscout.androidaps.database.TABLE_EFFECTIVE_PROFILE_SWITCHES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_EFFECTIVE_PROFILE_SWITCHES,
+ foreignKeys = [ForeignKey(
+ entity = EffectiveProfileSwitch::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class EffectiveProfileSwitch(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ override var duration: Long,
+ var basalBlocks: List
+) : TraceableDBEntry, DBEntryWithTimeAndDuration
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/ExtendedBolus.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/ExtendedBolus.kt
new file mode 100644
index 0000000000..40792c11c7
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/ExtendedBolus.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_EXTENDED_BOLUSES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_EXTENDED_BOLUSES,
+ foreignKeys = [ForeignKey(
+ entity = ExtendedBolus::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class ExtendedBolus(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ override var duration: Long,
+ var amount: Double,
+ var isEmulatingTempBasal: Boolean
+) : TraceableDBEntry, DBEntryWithTimeAndDuration
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt
new file mode 100644
index 0000000000..55716ba4ac
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt
@@ -0,0 +1,100 @@
+package info.nightscout.androidaps.database.entities
+
+import com.google.gson.annotations.SerializedName
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_GLUCOSE_VALUES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_GLUCOSE_VALUES,
+ foreignKeys = [ForeignKey(
+ entity = GlucoseValue::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class GlucoseValue(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var raw: Double?,
+ var value: Double,
+ var trendArrow: TrendArrow,
+ var noise: Double?,
+ var sourceSensor: SourceSensor
+) : TraceableDBEntry, DBEntryWithTime {
+
+ fun contentEqualsTo(other: GlucoseValue): Boolean =
+ timestamp == other.timestamp &&
+ utcOffset == other.utcOffset &&
+ raw == other.raw &&
+ value == other.value &&
+ trendArrow == other.trendArrow &&
+ noise == other.noise &&
+ sourceSensor == other.sourceSensor &&
+ isValid == other.isValid
+
+ fun isRecordDeleted(other: GlucoseValue): Boolean =
+ isValid && !other.isValid
+
+ enum class TrendArrow (val text:String, val symbol:String){
+ @SerializedName("NONE") NONE("NONE", "??"),
+ @SerializedName("TripleUp")TRIPLE_UP("TripleUp", "X"),
+ @SerializedName("DoubleUp")DOUBLE_UP("DoubleUp", "\u21c8"),
+ @SerializedName("SingleUp")SINGLE_UP("SingleUp", "\u2191"),
+ @SerializedName("FortyFiveUp")FORTY_FIVE_UP("FortyFiveUp", "\u2197"),
+ @SerializedName("Flat")FLAT("Flat", "\u2192"),
+ @SerializedName("FortyFiveDown")FORTY_FIVE_DOWN("FortyFiveDown", "\u2198"),
+ @SerializedName("SingleDown")SINGLE_DOWN("SingleDown", "\u2193"),
+ @SerializedName("DoubleDown")DOUBLE_DOWN("DoubleDown", "\u21ca"),
+ @SerializedName("TripleDown")TRIPLE_DOWN("TripleDown", "X")
+ ;
+
+ companion object {
+ fun fromString(direction : String?) = values().firstOrNull {it.text == direction} ?: NONE
+ }
+ }
+
+ enum class SourceSensor(val text : String) {
+ @SerializedName("AndroidAPS-Dexcom") DEXCOM_NATIVE_UNKNOWN("AndroidAPS-Dexcom"),
+ @SerializedName("AndroidAPS-DexcomG6") DEXCOM_G6_NATIVE("AndroidAPS-DexcomG6"),
+ @SerializedName("AndroidAPS-DexcomG5") DEXCOM_G5_NATIVE("AndroidAPS-DexcomG5"),
+ @SerializedName("Bluetooth Wixel") DEXCOM_G4_WIXEL("Bluetooth Wixel"),
+ @SerializedName("xBridge Wixel") DEXCOM_G4_XBRIDGE("xBridge Wixel"),
+ @SerializedName("G4 Share Receiver") DEXCOM_G4_NATIVE("G4 Share Receiver"),
+ @SerializedName("Medtrum A6") MEDTRUM_A6("Medtrum A6"),
+ @SerializedName("Network G4") DEXCOM_G4_NET("Network G4"),
+ @SerializedName("Network G4 and xBridge") DEXCOM_G4_NET_XBRIDGE("Network G4 and xBridge"),
+ @SerializedName("Network G4 and Classic xDrip") DEXCOM_G4_NET_CLASSIC("Network G4 and Classic xDrip"),
+ @SerializedName("DexcomG5") DEXCOM_G5_XDRIP("DexcomG5"),
+ @SerializedName("G6 Native") DEXCOM_G6_NATIVE_XDRIP("G6 Native"),
+ @SerializedName("G5 Native") DEXCOM_G5_NATIVE_XDRIP("G5 Native"),
+ @SerializedName("Network libre") LIBRE_1_NET("Network libre"),
+ @SerializedName("BlueReader") LIBRE_1_BLUE("BlueReader"),
+ @SerializedName("Transmiter PL") LIBRE_1_PL("Transmiter PL"),
+ @SerializedName("Blucon") LIBRE_1_BLUCON("Blucon"),
+ @SerializedName("Tomato") LIBRE_1_TOMATO("Tomato"),
+ @SerializedName("Rfduino") LIBRE_1_RF("Rfduino"),
+ @SerializedName("LimiTTer") LIBRE_1_LIMITTER("LimiTTer"),
+ @SerializedName("Glimp") GLIMP("Glimp"),
+ @SerializedName("Libre2") LIBRE_2_NATIVE("Libre2"),
+ @SerializedName("Poctech") POCTECH_NATIVE("Poctech"),
+ @SerializedName("MM600Series") MM_600_SERIES("MM600Series"),
+ @SerializedName("Eversense") EVERSENSE("Eversense"),
+ @SerializedName("Random") RANDOM("Random"),
+ @SerializedName("Unknown") UNKNOWN("Unknown")
+ ;
+
+ companion object {
+ fun fromString(source : String?) = values().firstOrNull {it.text == source} ?: UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/MealLink.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/MealLink.kt
new file mode 100644
index 0000000000..4c6a3c5f32
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/MealLink.kt
@@ -0,0 +1,54 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_MEAL_LINKS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+
+@Entity(tableName = TABLE_MEAL_LINKS,
+ foreignKeys = [ForeignKey(
+ entity = Bolus::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("bolusId")), ForeignKey(
+
+ entity = Carbs::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("carbsId")), ForeignKey(
+
+ entity = BolusCalculatorResult::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("bolusCalcResultId")), ForeignKey(
+
+ entity = TemporaryBasal::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("superbolusTempBasalId")), ForeignKey(
+
+ entity = TherapyEvent::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("noteId")), ForeignKey(
+
+ entity = MealLink::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("bolusId"),
+ Index("carbsId"), Index("bolusCalcResultId"),
+ Index("superbolusTempBasalId"), Index("noteId")])
+data class MealLink(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ var bolusId: Long? = null,
+ var carbsId: Long? = null,
+ var bolusCalcResultId: Long? = null,
+ var superbolusTempBasalId: Long? = null,
+ var noteId: Long? = null
+) : TraceableDBEntry {
+ override val foreignKeysValid: Boolean
+ get() = super.foreignKeysValid && bolusId != 0L && carbsId != 0L &&
+ bolusCalcResultId != 0L && superbolusTempBasalId != 0L && noteId != 0L
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/MultiwaveBolusLink.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/MultiwaveBolusLink.kt
new file mode 100644
index 0000000000..e8fbf135e0
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/MultiwaveBolusLink.kt
@@ -0,0 +1,37 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_MULTIWAVE_BOLUS_LINKS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+
+@Entity(tableName = TABLE_MULTIWAVE_BOLUS_LINKS,
+ foreignKeys = [ForeignKey(
+ entity = Bolus::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("bolusId")), ForeignKey(
+
+ entity = ExtendedBolus::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("extendedBolusId")), ForeignKey(
+
+ entity = MultiwaveBolusLink::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("bolusId"),
+ Index("extendedBolusId")])
+data class MultiwaveBolusLink(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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,
+ var bolusId: Long,
+ var extendedBolusId: Long
+) : TraceableDBEntry {
+ override val foreignKeysValid: Boolean
+ get() = super.foreignKeysValid && bolusId != 0L && bolusId != 0L && extendedBolusId != 0L
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/PreferenceChange.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/PreferenceChange.kt
new file mode 100644
index 0000000000..8ff67ccb71
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/PreferenceChange.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import info.nightscout.androidaps.database.TABLE_PREFERENCE_CHANGES
+import info.nightscout.androidaps.database.interfaces.DBEntry
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_PREFERENCE_CHANGES)
+data class PreferenceChange(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0L,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var key: String,
+ var value: Any?
+) : DBEntry, DBEntryWithTime
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/ProfileSwitch.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/ProfileSwitch.kt
new file mode 100644
index 0000000000..aa845fb8db
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/ProfileSwitch.kt
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.data.Block
+import info.nightscout.androidaps.database.TABLE_PROFILE_SWITCHES
+import info.nightscout.androidaps.database.data.TargetBlock
+import info.nightscout.androidaps.database.embedments.InsulinConfiguration
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_PROFILE_SWITCHES,
+ foreignKeys = [ForeignKey(
+ entity = ProfileSwitch::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class ProfileSwitch(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var profileName: String,
+ var glucoseUnit: GlucoseUnit,
+ var basalBlocks: List,
+ var isfBlocks: List,
+ var icBlocks: List,
+ var targetBlocks: List,
+ @Embedded
+ var insulinConfiguration: InsulinConfiguration,
+ var timeshift: Int,
+ var percentage: Int,
+ override var duration: Long
+) : TraceableDBEntry, DBEntryWithTimeAndDuration {
+ enum class GlucoseUnit {
+ MGDL,
+ MMOL
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryBasal.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryBasal.kt
new file mode 100644
index 0000000000..01f034ae86
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryBasal.kt
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_TEMPORARY_BASALS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_TEMPORARY_BASALS,
+ foreignKeys = [ForeignKey(
+ entity = TemporaryBasal::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class TemporaryBasal(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var type: Type,
+ var isAbsolute: Boolean,
+ var rate: Double,
+ override var duration: Long
+) : TraceableDBEntry, DBEntryWithTimeAndDuration {
+ enum class Type {
+ NORMAL,
+ EMULATED_PUMP_SUSPEND,
+ PUMP_SUSPEND,
+ SUPERBOLUS
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryTarget.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryTarget.kt
new file mode 100644
index 0000000000..96bde2e864
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/TemporaryTarget.kt
@@ -0,0 +1,68 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import com.google.gson.annotations.SerializedName
+import info.nightscout.androidaps.database.TABLE_TEMPORARY_TARGETS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.*
+
+@Entity(tableName = TABLE_TEMPORARY_TARGETS,
+ foreignKeys = [ForeignKey(
+ entity = TemporaryTarget::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class TemporaryTarget(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var reason: Reason,
+ var highTarget: Double, // in mgdl
+ var lowTarget: Double, // in mgdl
+ override var duration: Long // in millis
+) : TraceableDBEntry, DBEntryWithTimeAndDuration {
+
+ fun contentEqualsTo(other: TemporaryTarget): Boolean =
+ timestamp == other.timestamp &&
+ utcOffset == other.utcOffset &&
+ reason == other.reason &&
+ highTarget == other.highTarget &&
+ lowTarget == other.lowTarget &&
+ duration == other.duration &&
+ isValid == other.isValid
+
+ fun isRecordDeleted(other: TemporaryTarget): Boolean =
+ isValid && !other.isValid
+
+ enum class Reason(val text: String) {
+ @SerializedName("Custom")
+ CUSTOM("Custom"),
+ @SerializedName("Hypo")
+ HYPOGLYCEMIA("Hypo"),
+ @SerializedName("Activity")
+ ACTIVITY("Activity"),
+ @SerializedName("Eating Soon")
+ EATING_SOON("Eating Soon"),
+ @SerializedName("Automation")
+ AUTOMATION("Automation")
+ ;
+
+ companion object {
+ fun fromString(direction: String?) = values().firstOrNull { it.text == direction }
+ ?: CUSTOM
+ }
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/TherapyEvent.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/TherapyEvent.kt
new file mode 100644
index 0000000000..2225fed98f
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/TherapyEvent.kt
@@ -0,0 +1,63 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_THERAPY_EVENTS
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTimeAndDuration
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_THERAPY_EVENTS,
+ foreignKeys = [ForeignKey(
+ entity = TherapyEvent::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class TherapyEvent(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ override var duration: Long = 0,
+ var type: Type,
+ var note: String? = null,
+ var amount: Double? = null
+) : TraceableDBEntry, DBEntryWithTimeAndDuration {
+ enum class Type {
+ CANNULA_CHANGED,
+ TUBE_CHANGED,
+ RESERVOIR_CHANGED,
+ BATTERY_CHANGED,
+ LEAKING_INFUSION_SET,
+ SENSOR_INSERTED,
+ SENSOR_STARTED,
+ SENSOR_STOPPED,
+ FINGER_STICK_BG_VALUE,
+ ACTIVITY,
+ FALLING_ASLEEP,
+ WAKING_UP,
+ SICKNESS,
+ STRESS,
+ PRE_PERIOD,
+ ALCOHOL,
+ CORTISON,
+ FEELING_LOW,
+ FEELING_HIGH,
+ ANNOUNCEMENT,
+ QUESTION,
+ NOTE,
+ APS_OFFLINE,
+ BATTERY_EMPTY,
+ RESERVOIR_EMPTY,
+ OCCLUSION,
+ PUMP_STOPPED,
+ PUMP_STARTED,
+ PUMP_PAUSED
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/TotalDailyDose.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/TotalDailyDose.kt
new file mode 100644
index 0000000000..0cd450697c
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/TotalDailyDose.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.*
+import info.nightscout.androidaps.database.TABLE_TOTAL_DAILY_DOSES
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import info.nightscout.androidaps.database.interfaces.TraceableDBEntry
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_TOTAL_DAILY_DOSES,
+ foreignKeys = [ForeignKey(
+ entity = TotalDailyDose::class,
+ parentColumns = ["id"],
+ childColumns = ["referenceId"])],
+ indices = [Index("referenceId"), Index("timestamp")])
+data class TotalDailyDose(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0,
+ 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? = InterfaceIDs(),
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var basalAmount: Double?,
+ var bolusAmount: Double?,
+ var totalAmount: Double?
+) : TraceableDBEntry, DBEntryWithTime
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/VersionChange.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/VersionChange.kt
new file mode 100644
index 0000000000..83ca1ccc40
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/VersionChange.kt
@@ -0,0 +1,20 @@
+package info.nightscout.androidaps.database.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import info.nightscout.androidaps.database.TABLE_VERSION_CHANGES
+import info.nightscout.androidaps.database.interfaces.DBEntry
+import info.nightscout.androidaps.database.interfaces.DBEntryWithTime
+import java.util.TimeZone
+
+@Entity(tableName = TABLE_VERSION_CHANGES)
+data class VersionChange(
+ @PrimaryKey(autoGenerate = true)
+ override var id: Long = 0L,
+ override var timestamp: Long,
+ override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
+ var versionCode: Int,
+ var versionName: String,
+ var gitRemote: String?,
+ var commitHash: String?
+) : DBEntry, DBEntryWithTime
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntry.kt b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntry.kt
new file mode 100644
index 0000000000..f6c01287a6
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntry.kt
@@ -0,0 +1,5 @@
+package info.nightscout.androidaps.database.interfaces
+
+interface DBEntry {
+ var id: Long
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithDuration.kt b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithDuration.kt
new file mode 100644
index 0000000000..469ec17d6d
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithDuration.kt
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.database.interfaces
+
+interface DBEntryWithDuration {
+ var duration: Long
+
+ val durationUnknown get() = duration == Long.MAX_VALUE
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTime.kt b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTime.kt
new file mode 100644
index 0000000000..103942fcf0
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTime.kt
@@ -0,0 +1,6 @@
+package info.nightscout.androidaps.database.interfaces
+
+interface DBEntryWithTime {
+ var timestamp: Long
+ var utcOffset: Long
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTimeAndDuration.kt b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTimeAndDuration.kt
new file mode 100644
index 0000000000..b4f3f0bbbf
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/interfaces/DBEntryWithTimeAndDuration.kt
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.database.interfaces
+
+import kotlin.math.min
+
+interface DBEntryWithTimeAndDuration : DBEntryWithTime, DBEntryWithDuration
+
+var DBEntryWithTimeAndDuration.end
+ get() = timestamp + duration
+ set(value) {
+ duration = value - timestamp
+ }
+
+@JvmOverloads
+fun DBEntryWithTimeAndDuration.getRemainingDuration(current: Long = System.currentTimeMillis()) = min(0L, end - current)
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/interfaces/TraceableDBEntry.kt b/database/src/main/java/info/nightscout/androidaps/database/interfaces/TraceableDBEntry.kt
new file mode 100644
index 0000000000..7ab0ce0984
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/interfaces/TraceableDBEntry.kt
@@ -0,0 +1,28 @@
+package info.nightscout.androidaps.database.interfaces
+
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+
+interface TraceableDBEntry: DBEntry {
+ var version: Int
+ var dateCreated: Long
+ var isValid: Boolean
+ var referenceId: Long?
+ var interfaceIDs_backing: InterfaceIDs?
+
+ val historic: Boolean get() = referenceId != null
+
+ val foreignKeysValid: Boolean get() = referenceId != 0L
+
+ var interfaceIDs: InterfaceIDs
+ get() {
+ var value = this.interfaceIDs_backing
+ if (value == null) {
+ value = InterfaceIDs()
+ interfaceIDs_backing = value
+ }
+ return value
+ }
+ set(value) {
+ interfaceIDs_backing = value
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/CancelCurrentTemporaryTargetIfAnyTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/CancelCurrentTemporaryTargetIfAnyTransaction.kt
new file mode 100644
index 0000000000..206e9c1d1c
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/CancelCurrentTemporaryTargetIfAnyTransaction.kt
@@ -0,0 +1,16 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+import info.nightscout.androidaps.database.interfaces.end
+
+class CancelCurrentTemporaryTargetIfAnyTransaction(
+ val timestamp: Long
+) : Transaction() {
+ override fun run() {
+ val current = database.temporaryTargetDao.getTemporaryTargetActiveAt(timestamp)
+ if (current != null) {
+ current.end = timestamp
+ database.temporaryTargetDao.updateExistingEntry(current)
+ }
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/CgmSourceTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/CgmSourceTransaction.kt
new file mode 100644
index 0000000000..7a9a5a2dda
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/CgmSourceTransaction.kt
@@ -0,0 +1,74 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.database.entities.TherapyEvent
+
+/**
+ * Inserts data from a CGM source into the database
+ */
+class CgmSourceTransaction(
+ private val glucoseValues: List,
+ private val calibrations: List,
+ private val sensorInsertionTime: Long?
+) : Transaction>() {
+
+ override fun run(): List {
+ val insertedGlucoseValues = mutableListOf()
+ glucoseValues.forEach {
+ val current = database.glucoseValueDao.findByTimestampAndSensor(it.timestamp, it.sourceSensor)
+ val glucoseValue = GlucoseValue(
+ timestamp = it.timestamp,
+ raw = it.raw,
+ value = it.value,
+ noise = it.noise,
+ trendArrow = it.trendArrow,
+ sourceSensor = it.sourceSensor
+ )
+ glucoseValue.interfaceIDs.nightscoutId = it.nightscoutId
+ when {
+ current == null -> {
+ database.glucoseValueDao.insertNewEntry(glucoseValue)
+ insertedGlucoseValues.add(glucoseValue)
+ }
+
+ !current.contentEqualsTo(glucoseValue) -> {
+ glucoseValue.id = current.id
+ database.glucoseValueDao.updateExistingEntry(glucoseValue)
+ }
+ }
+ }
+ calibrations.forEach {
+ if (database.therapyEventDao.findByTimestamp(TherapyEvent.Type.FINGER_STICK_BG_VALUE, it.timestamp) == null) {
+ database.therapyEventDao.insertNewEntry(TherapyEvent(
+ timestamp = it.timestamp,
+ type = TherapyEvent.Type.FINGER_STICK_BG_VALUE,
+ amount = it.value
+ ))
+ }
+ }
+ sensorInsertionTime?.let {
+ if (database.therapyEventDao.findByTimestamp(TherapyEvent.Type.SENSOR_INSERTED, it) == null) {
+ database.therapyEventDao.insertNewEntry(TherapyEvent(
+ timestamp = it,
+ type = TherapyEvent.Type.SENSOR_INSERTED
+ ))
+ }
+ }
+ return insertedGlucoseValues
+ }
+
+ data class TransactionGlucoseValue(
+ val timestamp: Long,
+ val value: Double,
+ val raw: Double?,
+ val noise: Double?,
+ val trendArrow: GlucoseValue.TrendArrow,
+ val nightscoutId: String? = null,
+ val sourceSensor: GlucoseValue.SourceSensor
+ )
+
+ data class Calibration(
+ val timestamp: Long,
+ val value: Double
+ )
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertGlucoseValueTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertGlucoseValueTransaction.kt
new file mode 100644
index 0000000000..b75c4a3928
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertGlucoseValueTransaction.kt
@@ -0,0 +1,13 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.GlucoseValue
+
+/**
+ * Creates the GlucoseValue
+ */
+class InsertGlucoseValueTransaction(val glucoseValue: GlucoseValue) : Transaction() {
+
+ override fun run() {
+ database.glucoseValueDao.insert(glucoseValue)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertTemporaryTargetAndCancelCurrentTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertTemporaryTargetAndCancelCurrentTransaction.kt
new file mode 100644
index 0000000000..c3d42b3e52
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/InsertTemporaryTargetAndCancelCurrentTransaction.kt
@@ -0,0 +1,21 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+import info.nightscout.androidaps.database.interfaces.end
+
+class InsertTemporaryTargetAndCancelCurrentTransaction(
+ private val temporaryTarget: TemporaryTarget
+) : Transaction() {
+
+ constructor(timestamp: Long, duration: Long, reason: TemporaryTarget.Reason, lowTarget: Double, highTarget: Double) :
+ this(TemporaryTarget(timestamp = timestamp, reason = reason, lowTarget = lowTarget, highTarget = highTarget, duration = duration))
+
+ override fun run() {
+ val current = database.temporaryTargetDao.getTemporaryTargetActiveAt(temporaryTarget.timestamp)
+ if (current != null) {
+ current.end = temporaryTarget.timestamp
+ database.temporaryTargetDao.updateExistingEntry(current)
+ }
+ database.temporaryTargetDao.insertNewEntry(temporaryTarget)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateGlucoseValueTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateGlucoseValueTransaction.kt
new file mode 100644
index 0000000000..69895ed8bd
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateGlucoseValueTransaction.kt
@@ -0,0 +1,15 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.transactions.Transaction
+
+/**
+ * Invalidates the GlucoseValue with the specified id
+ */
+class InvalidateGlucoseValueTransaction(val id: Long) : Transaction() {
+ override fun run() {
+ val glucoseValue = database.glucoseValueDao.findById(id)
+ ?: throw IllegalArgumentException("There is no such GlucoseValue with the specified ID.")
+ glucoseValue.isValid = false
+ database.glucoseValueDao.updateExistingEntry(glucoseValue)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateTemporaryTargetTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateTemporaryTargetTransaction.kt
new file mode 100644
index 0000000000..7109fbf672
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateTemporaryTargetTransaction.kt
@@ -0,0 +1,10 @@
+package info.nightscout.androidaps.database.transactions
+
+class InvalidateTemporaryTargetTransaction(val id: Long) : Transaction() {
+ override fun run() {
+ val temporaryTarget = database.temporaryTargetDao.findById(id)
+ ?: throw IllegalArgumentException("There is no such TemporaryTarget with the specified ID.")
+ temporaryTarget.isValid = false
+ database.temporaryTargetDao.updateExistingEntry(temporaryTarget)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/Transaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/Transaction.kt
new file mode 100644
index 0000000000..36d07a9926
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/Transaction.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.DelegatedAppDatabase
+
+/**
+ * Base class for database transactions
+ * @param T The return type of the Transaction
+ */
+abstract class Transaction {
+
+ /**
+ * Executes the Transaction
+ */
+ internal abstract fun run(): T
+
+ internal lateinit var database: DelegatedAppDatabase
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateGlucoseValueTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateGlucoseValueTransaction.kt
new file mode 100644
index 0000000000..8d6ce188be
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateGlucoseValueTransaction.kt
@@ -0,0 +1,13 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.GlucoseValue
+
+/**
+ * Updates the GlucoseValue
+ */
+class UpdateGlucoseValueTransaction(val glucoseValue: GlucoseValue) : Transaction() {
+
+ override fun run() {
+ database.glucoseValueDao.updateExistingEntry(glucoseValue)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateTemporaryTargetTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateTemporaryTargetTransaction.kt
new file mode 100644
index 0000000000..0a9f140c20
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/UpdateTemporaryTargetTransaction.kt
@@ -0,0 +1,13 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.TemporaryTarget
+
+/**
+ * Updates the TemporaryTarget
+ */
+class UpdateTemporaryTargetTransaction(private val temporaryTarget: TemporaryTarget) : Transaction() {
+
+ override fun run() {
+ database.temporaryTargetDao.updateExistingEntry(temporaryTarget)
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/VersionChangeTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/VersionChangeTransaction.kt
new file mode 100644
index 0000000000..d828e279b3
--- /dev/null
+++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/VersionChangeTransaction.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.database.transactions
+
+import info.nightscout.androidaps.database.entities.VersionChange
+import java.util.*
+
+class VersionChangeTransaction(
+ private val versionName: String,
+ private val versionCode: Int,
+ private val gitRemote: String?,
+ private val commitHash: String?) : Transaction() {
+
+ override fun run() {
+ val current = database.versionChangeDao.getMostRecentVersionChange()
+ if (current == null
+ || current.versionName != versionName
+ || current.versionCode != versionCode
+ || current.gitRemote != gitRemote
+ || current.commitHash != commitHash) {
+ val currentTime = System.currentTimeMillis()
+ database.versionChangeDao.insert(VersionChange(
+ timestamp = System.currentTimeMillis(),
+ versionCode = versionCode,
+ versionName = versionName,
+ gitRemote = gitRemote,
+ commitHash = commitHash
+ ))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/database/src/test/java/info/nightscout/database/ExampleUnitTest.kt b/database/src/test/java/info/nightscout/database/ExampleUnitTest.kt
new file mode 100644
index 0000000000..a0b6e3a55e
--- /dev/null
+++ b/database/src/test/java/info/nightscout/database/ExampleUnitTest.kt
@@ -0,0 +1,18 @@
+package info.nightscout.database
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 4fb6743f5f..163be2365a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
+include ':database'
include ':app', ':wear', ':core', ':dana', ':danar', ':danars', ':rileylink', ':medtronic', ':omnipod'