diff options
| -rw-r--r-- | .coverage-baseline | 2 | ||||
| -rw-r--r-- | app/src/androidTest/java/org/terst/listen/data/LibraryOrderTest.kt | 23 | ||||
| -rw-r--r-- | app/src/main/java/org/terst/listen/data/AppDatabase.kt | 58 |
3 files changed, 57 insertions, 26 deletions
diff --git a/.coverage-baseline b/.coverage-baseline index e44f50b..b486fa4 100644 --- a/.coverage-baseline +++ b/.coverage-baseline @@ -1 +1 @@ -65.18 +65.04 diff --git a/app/src/androidTest/java/org/terst/listen/data/LibraryOrderTest.kt b/app/src/androidTest/java/org/terst/listen/data/LibraryOrderTest.kt index a181dd7..4117b79 100644 --- a/app/src/androidTest/java/org/terst/listen/data/LibraryOrderTest.kt +++ b/app/src/androidTest/java/org/terst/listen/data/LibraryOrderTest.kt @@ -119,6 +119,29 @@ class LibraryOrderTest { assertEquals(listOf("b", "a"), order()) } + /** + * A fresh install gets the default filters too. + * + * Found on a device: a reinstall creates the schema at the current version and never runs a + * migration, so seeding only in MIGRATION_4_5 quietly gave them to upgraders alone. + */ + @Test + fun aFreshDatabaseIsSeededWithTheDefaultFilters() = runBlocking { + val fresh = Room.inMemoryDatabaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + ).addCallback(AppDatabase.SEED_DEFAULT_FILTERS).build() + + try { + // Room creates the database lazily, so the callback has not run until it is used. + val rules = fresh.rules().getAll() + assertEquals(listOf("trailer", "preview"), rules.map { it.titleContains }) + assertEquals(listOf(RuleAction.MARK_PLAYED, RuleAction.MARK_PLAYED), rules.map { it.action }) + } finally { + fresh.close() + } + } + /** A show with nothing waiting sorts last, by title, rather than vanishing. */ @Test fun aShowWithNothingWaitingSortsLast() = runBlocking { diff --git a/app/src/main/java/org/terst/listen/data/AppDatabase.kt b/app/src/main/java/org/terst/listen/data/AppDatabase.kt index c33921b..fe73ce0 100644 --- a/app/src/main/java/org/terst/listen/data/AppDatabase.kt +++ b/app/src/main/java/org/terst/listen/data/AppDatabase.kt @@ -101,37 +101,44 @@ abstract class AppDatabase : RoomDatabase() { /** * Seeds the trailer and preview filters Peter asked for. * - * Rules live in the database on the phone, so a migration is the only way to put them - * there. They are ordinary rules: visible in the Rules screen, and editable, disableable - * or deletable like any other. Appended after whatever is already there rather than + * Rules live in the database on the phone, so code is the only way to put them there. + * They are ordinary rules: visible in the Rules screen, and editable, disableable or + * deletable like any other. Appended after whatever is already there rather than * taking position 0, so they cannot pre-empt rules already being relied on, and they * only act on newly discovered episodes -- nothing already in the library changes. * - * INSERT OR IGNORE on a fixed id, so deleting one does not bring it back on the next - * upgrade. + * INSERT OR IGNORE on a fixed id, so deleting one does not bring it back. */ + private fun seedDefaultFilters(db: SupportSQLiteDatabase) { + seedFilter(db, "listen-default-trailer", "Trailers", "trailer") + seedFilter(db, "listen-default-preview", "Previews", "preview") + } + + private fun seedFilter(db: SupportSQLiteDatabase, id: String, name: String, word: String) { + db.execSQL( + "INSERT OR IGNORE INTO rules " + + "(id, name, position, enabled, subscriptionId, titleContains, " + + "titleExcludes, minDurationMs, maxDurationMs, maxAgeMs, seasonNumber, " + + "action, autoDownload, speed) " + + "SELECT ?, ?, COALESCE(MAX(position), -1) + 1, 1, NULL, ?, NULL, " + + "NULL, NULL, NULL, NULL, 'MARK_PLAYED', 0, NULL FROM rules", + arrayOf(id, name, word), + ) + } + val MIGRATION_4_5 = object : Migration(4, 5) { - override fun migrate(db: SupportSQLiteDatabase) { - seedFilter(db, "listen-default-trailer", "Trailers", "trailer") - seedFilter(db, "listen-default-preview", "Previews", "preview") - } + override fun migrate(db: SupportSQLiteDatabase) = seedDefaultFilters(db) + } - private fun seedFilter( - db: SupportSQLiteDatabase, - id: String, - name: String, - word: String, - ) { - db.execSQL( - "INSERT OR IGNORE INTO rules " + - "(id, name, position, enabled, subscriptionId, titleContains, " + - "titleExcludes, minDurationMs, maxDurationMs, maxAgeMs, seasonNumber, " + - "action, autoDownload, speed) " + - "SELECT ?, ?, COALESCE(MAX(position), -1) + 1, 1, NULL, ?, NULL, " + - "NULL, NULL, NULL, NULL, 'MARK_PLAYED', 0, NULL FROM rules", - arrayOf(id, name, word), - ) - } + /** + * The same filters for a fresh install, which never runs a migration. + * + * Found on a device: a reinstall creates the schema at the current version directly, so + * seeding only in MIGRATION_4_5 gave the filters to upgraders and silently to nobody + * else. + */ + val SEED_DEFAULT_FILTERS = object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) = seedDefaultFilters(db) } fun get(context: Context): AppDatabase = instance ?: synchronized(this) { @@ -143,6 +150,7 @@ abstract class AppDatabase : RoomDatabase() { // Listed one by one rather than spread from an array: every migration here is a // deliberate addition, and the list is the record of them. .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) + .addCallback(SEED_DEFAULT_FILTERS) // No fallbackToDestructiveMigration. A listener's playback positions and play // history are not reproducible from anywhere, so a missing migration must fail // loudly rather than silently wipe the library. |
