diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt index c8435c88..1eadea4f 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt @@ -60,4 +60,7 @@ interface Look4SatDao { @Query("DELETE FROM radios") suspend fun deleteRadios() + + @Query("DELETE FROM radios WHERE isCustom = 0") + suspend fun deleteManagedRadios() } diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDb.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDb.kt index 43b22955..688de323 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDb.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDb.kt @@ -19,19 +19,21 @@ package com.rtbishop.look4sat.core.data.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.rtbishop.look4sat.core.data.database.entity.SatEntry import com.rtbishop.look4sat.core.data.database.entity.SatRadio -@Database(entities = [SatEntry::class, SatRadio::class], version = 1, exportSchema = false) +@Database(entities = [SatEntry::class, SatRadio::class], version = 2, exportSchema = false) abstract class Look4SatDb : RoomDatabase() { abstract fun look4SatDao(): Look4SatDao } -//val MIGRATION_1_2 = object : Migration(1, 2) { -// override fun migrate(database: SupportSQLiteDatabase) { -// database.execSQL("CREATE TABLE entries_backup (name TEXT NOT NULL, epoch REAL NOT NULL, meanmo REAL NOT NULL, eccn REAL NOT NULL, incl REAL NOT NULL, raan REAL NOT NULL, argper REAL NOT NULL, meanan REAL NOT NULL, catnum INTEGER NOT NULL, bstar REAL NOT NULL, xincl REAL NOT NULL, xnodeo REAL NOT NULL, omegao REAL NOT NULL, xmo REAL NOT NULL, xno REAL NOT NULL, orbitalPeriod REAL NOT NULL, isDeepSpace INTEGER NOT NULL, comment TEXT, PRIMARY KEY(catnum))") -// database.execSQL("INSERT INTO entries_backup (name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, orbitalPeriod, isDeepSpace, comment) SELECT name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, 1440 / meanmo, 1440 / meanmo >= 225.0, comment FROM entries") -// database.execSQL("DROP TABLE entries") -// database.execSQL("ALTER TABLE entries_backup RENAME TO entries") -// } -//} +/** Adds the mean motion derivative, needed to tell decayed satellites apart, and marks the + * transceivers that were imported from a file, so that remote updates leave them alone. */ +val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE entries ADD COLUMN ndot REAL NOT NULL DEFAULT 0.0") + db.execSQL("ALTER TABLE radios ADD COLUMN isCustom INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatEntry.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatEntry.kt index 117356b5..211d1c3a 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatEntry.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatEntry.kt @@ -30,5 +30,6 @@ data class SatEntry( val argper: Double, val meanan: Double, val catnum: Int, - val bstar: Double + val bstar: Double, + val ndot: Double = 0.0 ) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt index ba9eccdf..6e3a3476 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt @@ -32,5 +32,7 @@ data class SatRadio( val uplinkHigh: Long?, val uplinkMode: String?, val isInverted: Boolean, - val catnum: Int? + val catnum: Int?, + /** Set for manually imported transceivers, which remote updates must not replace. */ + val isCustom: Boolean = false ) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/injection/MainContainer.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/injection/MainContainer.kt index 8bc0a811..6948e52a 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/injection/MainContainer.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/injection/MainContainer.kt @@ -24,6 +24,7 @@ import android.hardware.display.DisplayManager import android.location.LocationManager import androidx.room.Room import com.rtbishop.look4sat.core.data.database.Look4SatDb +import com.rtbishop.look4sat.core.data.database.MIGRATION_1_2 import com.rtbishop.look4sat.core.data.framework.BluetoothReporter import com.rtbishop.look4sat.core.data.framework.Ft817Controller import com.rtbishop.look4sat.core.data.framework.Ic705Controller @@ -167,7 +168,10 @@ class MainContainer(private val context: Context) : IMainContainer { private fun provideLocalSource(): ILocalSource { val builder = Room.databaseBuilder(context, Look4SatDb::class.java, "Look4SatDBv400") - val database = builder.fallbackToDestructiveMigration(false).build() + val database = builder + .addMigrations(MIGRATION_1_2) + .fallbackToDestructiveMigration(false) + .build() return LocalSource(database.look4SatDao()) } diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt index 9c056729..48dffeee 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt @@ -58,7 +58,7 @@ class DatabaseRepo( var importedCount = 0 remoteSource.getFileStream(uri)?.let { stream -> val transceivers = dataParser.parseJSONStream(unwrapIfZipped(uri, stream)) - localSource.insertRadios(transceivers) + localSource.insertRadios(transceivers, isCustom = true) importedCount = transceivers.size } setUpdateSuccessful(System.currentTimeMillis()) @@ -105,9 +105,16 @@ class DatabaseRepo( val normUrl = normalizeUrl(rawUrl) result.stream?.let { dataParser.parseJSONStream(unwrapIfZipped(normUrl, it)) }.orEmpty() }.filter { it.uuid.isNotBlank() }.distinctBy { it.uuid } - // insert parsed data into the database - localSource.insertEntries(importedEntries) - localSource.insertRadios(importedRadios) + // insert parsed data into the database. + // Transceivers are a full snapshot: sources publish active entries only, so a retired + // transceiver simply disappears from the feed and has to be dropped locally as well. + // Manually imported ones (isCustom) are kept: no source can refresh them, so nothing + // would bring them back if they were replaced here. + if (importedRadios.isNotEmpty()) { + localSource.deleteManagedRadios() + localSource.insertRadios(importedRadios) + } + if (importedEntries.isNotEmpty()) localSource.insertEntries(importedEntries) setUpdateSuccessful(System.currentTimeMillis()) } diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/source/LocalSource.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/source/LocalSource.kt index b377e807..afe43a1b 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/source/LocalSource.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/source/LocalSource.kt @@ -51,12 +51,12 @@ class LocalSource(private val look4SatDao: Look4SatDao) : ILocalSource { private fun FrameworkEntry.toDomain() = OrbitalData( this.name, this.epoch, this.meanmo, this.eccn, this.incl, - this.raan, this.argper, this.meanan, this.catnum, this.bstar + this.raan, this.argper, this.meanan, this.catnum, this.bstar, this.ndot ) private fun OrbitalData.toEntity() = FrameworkEntry( this.name, this.epoch, this.meanmo, this.eccn, this.incl, - this.raan, this.argper, this.meanan, this.catnum, this.bstar + this.raan, this.argper, this.meanan, this.catnum, this.bstar, this.ndot ) private fun List.toEntity() = this.map { item -> item.toEntity() } @@ -73,20 +73,22 @@ class LocalSource(private val look4SatDao: Look4SatDao) : ILocalSource { return look4SatDao.getRadiosWithId(id).toDomainRadios() } - override suspend fun insertRadios(radios: List) { - look4SatDao.insertRadios(radios.toFrameworkRadios()) + override suspend fun insertRadios(radios: List, isCustom: Boolean) { + look4SatDao.insertRadios(radios.map { it.copy(isCustom = isCustom) }.toFrameworkRadios()) } + override suspend fun deleteManagedRadios() = look4SatDao.deleteManagedRadios() + override suspend fun deleteRadios() = look4SatDao.deleteRadios() private fun DomainRadio.toFramework() = FrameworkRadio( this.uuid, this.info, this.isAlive, this.downlinkLow, this.downlinkHigh, this.downlinkMode, - this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum + this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum, this.isCustom ) private fun FrameworkRadio.toDomain() = DomainRadio( this.uuid, this.info, this.isAlive, this.downlinkLow, this.downlinkHigh, this.downlinkMode, - this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum + this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum, this.isCustom ) private fun List.toFrameworkRadios() = this.map { radio -> radio.toFramework() } diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt index c6782091..10b37836 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt @@ -185,8 +185,12 @@ private class FakeLocalSource : ILocalSource { override suspend fun getRadiosWithId(id: Int): List = emptyList() - override suspend fun insertRadios(radios: List) { - insertedRadios += radios + override suspend fun insertRadios(radios: List, isCustom: Boolean) { + insertedRadios += radios.map { it.copy(isCustom = isCustom) } + } + + override suspend fun deleteManagedRadios() { + insertedRadios.removeAll { !it.isCustom } } override suspend fun deleteRadios() { diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt index b057fb4e..38d114d3 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt @@ -32,5 +32,7 @@ data class SatRadio( @SerialName("uplink_high") val uplinkHigh: Long?, @SerialName("uplink_mode") val uplinkMode: String?, @SerialName("invert") val isInverted: Boolean, - @SerialName("norad_cat_id") val catnum: Int? + @SerialName("norad_cat_id") val catnum: Int?, + /** Set for manually imported transceivers, which remote updates must not replace. */ + val isCustom: Boolean = false ) diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/ILocalSource.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/ILocalSource.kt index 93e403a5..246c09f0 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/ILocalSource.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/ILocalSource.kt @@ -31,6 +31,9 @@ interface ILocalSource { suspend fun getIdsWithModes(modes: List): List suspend fun getRadiosTotal(): Int suspend fun getRadiosWithId(id: Int): List - suspend fun insertRadios(radios: List) + suspend fun insertRadios(radios: List, isCustom: Boolean = false) + + /** Delete all non-custom transceivers, keeping manually imported ones. */ + suspend fun deleteManagedRadios() suspend fun deleteRadios() }