diff options
7 files changed, 283 insertions, 7 deletions
diff --git a/app/build.gradle b/app/build.gradle index c6e1aef8..2a0f7768 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -314,7 +314,7 @@ dependencies { implementation "androidx.room:room-ktx:${Versions.room}" implementation "androidx.room:room-runtime:${Versions.room}" kapt "androidx.room:room-compiler:${Versions.room}" - testImplementation "androidx.room:room-testing:${Versions.room}" + androidTestImplementation "androidx.room:room-testing:${Versions.room}" } diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt new file mode 100644 index 00000000..6ba6e0b6 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieMigrationTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.pitchedapps.frost.db + +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import kotlin.test.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CookieMigrationTest { + + private val TEST_DB = "cookie_migration_test" + + private val ALL_MIGRATIONS = arrayOf(COOKIES_MIGRATION_1_2) + + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + FrostPrivateDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() + ) + + @Test + fun migrateAll() { + // Create earliest version of the database. + helper.createDatabase(TEST_DB, 1).apply { + close() + } + + // Open latest version of the database. Room will validate the schema + // once all migrations execute. + Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + FrostPrivateDatabase::class.java, + TEST_DB + ).addMigrations(*ALL_MIGRATIONS).build().apply { + openHelper.writableDatabase + close() + } + } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 388edfe6..163d151b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -23,6 +23,8 @@ import androidx.room.Entity import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.pitchedapps.frost.prefs.Prefs import kotlinx.android.parcel.Parcelize @@ -37,7 +39,8 @@ data class CookieEntity( @ColumnInfo(name = "cookie_id") val id: Long, val name: String?, - val cookie: String? + val cookie: String?, + val cookieMessenger: String? = null // Version 2 ) : Parcelable { override fun toString(): String = "CookieEntity(${hashCode()})" @@ -61,6 +64,9 @@ interface CookieDao { @Query("DELETE FROM cookies WHERE cookie_id = :id") fun _deleteById(id: Long) + + @Query("UPDATE cookies SET cookieMessenger = :cookie WHERE cookie_id = :id") + fun _updateMessengerCookie(id: Long, cookie: String?) } suspend fun CookieDao.selectAll() = dao { _selectAll() } @@ -69,3 +75,11 @@ suspend fun CookieDao.save(cookie: CookieEntity) = dao { _save(cookie) } suspend fun CookieDao.save(cookies: List<CookieEntity>) = dao { _save(cookies) } suspend fun CookieDao.deleteById(id: Long) = dao { _deleteById(id) } suspend fun CookieDao.currentCookie(prefs: Prefs) = selectById(prefs.userId) +suspend fun CookieDao.updateMessengerCookie(id: Long, cookie: String?) = + dao { _updateMessengerCookie(id, cookie) } + +val COOKIES_MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE cookies ADD COLUMN cookieMessenger TEXT") + } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 21a2f1dc..bd0b4ee0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -31,7 +31,7 @@ interface FrostPrivateDao { @Database( entities = [CookieEntity::class, NotificationEntity::class, CacheEntity::class], - version = 1, + version = 2, exportSchema = true ) abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao { @@ -84,7 +84,7 @@ class FrostDatabase( val privateDb = Room.databaseBuilder( context, FrostPrivateDatabase::class.java, FrostPrivateDatabase.DATABASE_NAME - ).frostBuild() + ).addMigrations(COOKIES_MIGRATION_1_2).frostBuild() val publicDb = Room.databaseBuilder( context, FrostPublicDatabase::class.java, FrostPublicDatabase.DATABASE_NAME diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index efaa03ab..db40495b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -24,6 +24,7 @@ import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.deleteById import com.pitchedapps.frost.db.save import com.pitchedapps.frost.db.selectById +import com.pitchedapps.frost.db.updateMessengerCookie import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.cookies @@ -115,14 +116,16 @@ class FbCookie(private val prefs: Prefs, private val cookieDao: CookieDao) { L.d { "Switching User; null cookie" } return } + val currentId = prefs.userId withContext(Dispatchers.IO + NonCancellable) { L.d { "Switching User" } - // TODO save old messenger cookie + // Save current messenger cookie state. + cookieDao.updateMessengerCookie(currentId, messengerCookie) prefs.userId = cookie.id CookieManager.getInstance().apply { removeAllCookies() suspendSetWebCookie(FB_COOKIE_DOMAIN, cookie.cookie) - // TODO set messenger cookie + suspendSetWebCookie(MESSENGER_COOKIE_DOMAIN, cookie.cookieMessenger) flush() } } diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/2.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/2.json new file mode 100644 index 00000000..066f4478 --- /dev/null +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/2.json @@ -0,0 +1,201 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "c7625cc0226e291f14c14f528a11e739", + "entities": [ + { + "tableName": "cookies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cookie_id` INTEGER NOT NULL, `name` TEXT, `cookie` TEXT, `cookieMessenger` TEXT, PRIMARY KEY(`cookie_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "cookie_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cookie", + "columnName": "cookie", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cookieMessenger", + "columnName": "cookieMessenger", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "cookie_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notif_id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `href` TEXT NOT NULL, `title` TEXT, `text` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `profileUrl` TEXT, `type` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`notif_id`, `userId`), FOREIGN KEY(`userId`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "notif_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "href", + "columnName": "href", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileUrl", + "columnName": "profileUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "notif_id", + "userId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notifications_notif_id", + "unique": false, + "columnNames": [ + "notif_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notifications_notif_id` ON `${TABLE_NAME}` (`notif_id`)" + }, + { + "name": "index_notifications_userId", + "unique": false, + "columnNames": [ + "userId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_notifications_userId` ON `${TABLE_NAME}` (`userId`)" + } + ], + "foreignKeys": [ + { + "table": "cookies", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "userId" + ], + "referencedColumns": [ + "cookie_id" + ] + } + ] + }, + { + "tableName": "frost_cache", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, `contents` TEXT NOT NULL, PRIMARY KEY(`id`, `type`), FOREIGN KEY(`id`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contents", + "columnName": "contents", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id", + "type" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "cookies", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "id" + ], + "referencedColumns": [ + "cookie_id" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c7625cc0226e291f14c14f528a11e739')" + ] + } +}
\ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6ed9d7b9..fc6aae95 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -16,7 +16,7 @@ object Versions { // https://square.github.io/okhttp/changelog/ const val okhttp = "4.9.0" // https://developer.android.com/jetpack/androidx/releases/room - const val room = "2.2.5" + const val room = "2.2.6" // http://robolectric.org/getting-started/ const val roboelectric = "4.4" // https://github.com/davemorrissey/subsampling-scale-image-view#quick-start |