From d9611360b20b2aa722bae802c504d716f4021e13 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:37:02 +0200 Subject: [PATCH 001/120] Started Dao Migration Replaced greenDAO dependency with androidx.room Introduced database version 13 --- build.gradle | 1 - buildsystem/dependencies.gradle | 6 +- data/build.gradle | 38 ++- .../13.json | 229 ++++++++++++++++++ .../data/db/UpgradeDatabaseTest.kt | 2 + .../java/org/cryptomator/data/db/CloudDao.kt | 34 +++ .../data/db/CompoundDatabaseUpgrade.java | 21 -- .../data/db/CryptomatorDatabase.kt | 21 ++ .../org/cryptomator/data/db/Database.java | 53 ---- .../cryptomator/data/db/DatabaseFactory.java | 57 ----- .../org/cryptomator/data/db/DatabaseModule.kt | 70 ++++++ .../cryptomator/data/db/DatabaseUpgrade.java | 41 ---- .../cryptomator/data/db/DatabaseUpgrades.java | 94 ------- .../java/org/cryptomator/data/db/RowId.kt | 3 + .../java/org/cryptomator/data/db/Sql.java | 133 +++++----- .../org/cryptomator/data/db/UpdateCheckDao.kt | 34 +++ .../org/cryptomator/data/db/Upgrade0To1.kt | 19 +- .../org/cryptomator/data/db/Upgrade10To11.kt | 14 +- .../org/cryptomator/data/db/Upgrade11To12.kt | 7 +- .../org/cryptomator/data/db/Upgrade1To2.kt | 11 +- .../org/cryptomator/data/db/Upgrade2To3.kt | 7 +- .../org/cryptomator/data/db/Upgrade3To4.kt | 11 +- .../org/cryptomator/data/db/Upgrade4To5.kt | 11 +- .../org/cryptomator/data/db/Upgrade5To6.kt | 11 +- .../org/cryptomator/data/db/Upgrade6To7.kt | 11 +- .../org/cryptomator/data/db/Upgrade7To8.kt | 11 +- .../org/cryptomator/data/db/Upgrade8To9.kt | 7 +- .../org/cryptomator/data/db/Upgrade9To10.kt | 7 +- .../java/org/cryptomator/data/db/VaultDao.kt | 33 +++ .../data/db/entities/CloudEntity.java | 119 --------- .../data/db/entities/CloudEntity.kt | 20 ++ .../data/db/entities/DatabaseEntity.java | 10 - .../data/db/entities/DatabaseEntity.kt | 6 + .../data/db/entities/UpdateCheckEntity.java | 95 -------- .../data/db/entities/UpdateCheckEntity.kt | 17 ++ .../data/db/entities/VaultEntity.java | 215 ---------------- .../data/db/entities/VaultEntity.kt | 24 ++ .../data/db/mappers/CloudEntityMapper.java | 4 +- .../data/db/mappers/VaultEntityMapper.java | 36 +-- .../data/db/migrations/Migration12To13.kt | 12 + .../data/repository/CloudRepositoryImpl.java | 15 +- .../data/repository/RepositoryModule.java | 3 +- .../repository/UpdateCheckRepositoryImpl.java | 20 +- .../data/repository/VaultRepositoryImpl.java | 21 +- presentation/build.gradle | 3 + 45 files changed, 721 insertions(+), 896 deletions(-) create mode 100644 data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json create mode 100644 data/src/main/java/org/cryptomator/data/db/CloudDao.kt delete mode 100755 data/src/main/java/org/cryptomator/data/db/CompoundDatabaseUpgrade.java create mode 100644 data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt delete mode 100644 data/src/main/java/org/cryptomator/data/db/Database.java delete mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseFactory.java create mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt delete mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseUpgrade.java delete mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java create mode 100644 data/src/main/java/org/cryptomator/data/db/RowId.kt create mode 100644 data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt create mode 100644 data/src/main/java/org/cryptomator/data/db/VaultDao.kt delete mode 100644 data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java create mode 100644 data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt delete mode 100755 data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.java create mode 100644 data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.kt delete mode 100644 data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.java create mode 100644 data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt delete mode 100644 data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java create mode 100644 data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt create mode 100644 data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt diff --git a/build.gradle b/build.gradle index 6c568e557..b0c133fb0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.4.2' - classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1" diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index fe2662a7f..4757a7791 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -59,8 +59,7 @@ ext { lruFileCacheVersion = '1.2' - // KEEP IN SYNC WITH GENERATOR VERSION IN root build.gradle - greenDaoVersion = '3.3.0' + roomVersion = '2.5.2' // cloud provider libs cryptolibVersion = '2.1.2' @@ -143,7 +142,8 @@ ext { googlePlayServicesAuth: "com.google.android.gms:play-services-auth:${googlePlayServicesVersion}", trackingFreeGoogleCLient : files("lib/google-http-client-${trackingFreeGoogleCLientVersion}.jar"), trackingFreeGoogleAndroidCLient: files("lib/google-http-client-android-${trackingFreeGoogleCLientVersion}.jar"), - greenDao : "org.greenrobot:greendao:${greenDaoVersion}", + room : "androidx.room:room-runtime:${roomVersion}", + roomCompiler : "androidx.room:room-compiler:${roomVersion}", gson : "com.google.code.gson:gson:${gsonVersion}", hamcrest : "org.hamcrest:hamcrest-all:${hamcrestVersion}", javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}", diff --git a/data/build.gradle b/data/build.gradle index 596b438a3..4810c574c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -1,6 +1,6 @@ -apply plugin: 'org.greenrobot.greendao' apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'de.mannodermaus.android-junit5' android { @@ -17,6 +17,14 @@ android { buildConfigField "String", "VERSION_NAME", "\"${globalConfiguration["androidVersionName"]}\"" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + + javaCompileOptions { + annotationProcessorOptions { + compilerArgumentProviders( + new RoomSchemaArgProvider(new File(projectDir, "room/schemas")) + ) + } + } } compileOptions { @@ -89,10 +97,6 @@ android { namespace 'org.cryptomator.data' } -greendao { - schemaVersion 12 -} - configurations.all { // Check for updates every build (use for cryptolib snapshot) //resolutionStrategy.cacheChangingModulesFor 0, 'seconds' @@ -112,10 +116,14 @@ dependencies { // cryptomator implementation dependencies.cryptolib - // greendao - api dependencies.greenDao + // room + implementation dependencies.room + annotationProcessor dependencies.roomCompiler + kapt dependencies.roomCompiler + // dagger annotationProcessor dependencies.daggerCompiler + kapt dependencies.daggerCompiler implementation dependencies.dagger api dependencies.jsonWebTokenApi @@ -244,3 +252,19 @@ tasks.withType(Test) { showStandardStreams = false } } + +class RoomSchemaArgProvider implements CommandLineArgumentProvider { + + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + File schemaDir + + RoomSchemaArgProvider(File schemaDir) { + this.schemaDir = schemaDir + } + + @Override + Iterable asArguments() { + return ["-Aroom.schemaLocation=${schemaDir.path}".toString()] + } +} \ No newline at end of file diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json new file mode 100644 index 000000000..00c61a7a8 --- /dev/null +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json @@ -0,0 +1,229 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "ab8cd826759896443cfcac2f825a509c", + "entities": [ + { + "tableName": "CLOUD_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `TYPE` TEXT NOT NULL, `ACCESS_TOKEN` TEXT, `URL` TEXT, `USERNAME` TEXT, `WEBDAV_CERTIFICATE` TEXT, `S3_BUCKET` TEXT, `S3_REGION` TEXT, `S3_SECRET_KEY` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "TYPE", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "ACCESS_TOKEN", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "URL", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "USERNAME", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webdavCertificate", + "columnName": "WEBDAV_CERTIFICATE", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Bucket", + "columnName": "S3_BUCKET", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Region", + "columnName": "S3_REGION", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3SecretKey", + "columnName": "S3_SECRET_KEY", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UPDATE_CHECK_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LICENSE_TOKEN` TEXT, `RELEASE_NOTE` TEXT, `VERSION` TEXT, `URL_TO_APK` TEXT, `APK_SHA256` TEXT, `URL_TO_RELEASE_NOTE` TEXT, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "licenseToken", + "columnName": "LICENSE_TOKEN", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "releaseNote", + "columnName": "RELEASE_NOTE", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "VERSION", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToApk", + "columnName": "URL_TO_APK", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "apkSha256", + "columnName": "APK_SHA256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToReleaseNote", + "columnName": "URL_TO_RELEASE_NOTE", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "VAULT_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `FOLDER_CLOUD_ID` INTEGER, `FOLDER_PATH` TEXT, `FOLDER_NAME` TEXT, `CLOUD_TYPE` TEXT NOT NULL, `PASSWORD` TEXT, `POSITION` INTEGER, `FORMAT` INTEGER, `SHORTENING_THRESHOLD` INTEGER, PRIMARY KEY(`_id`), FOREIGN KEY(`FOLDER_CLOUD_ID`) REFERENCES `CLOUD_ENTITY`(`_id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderCloudId", + "columnName": "FOLDER_CLOUD_ID", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderPath", + "columnName": "FOLDER_PATH", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folderName", + "columnName": "FOLDER_NAME", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cloudType", + "columnName": "CLOUD_TYPE", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "PASSWORD", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "POSITION", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "FORMAT", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shorteningThreshold", + "columnName": "SHORTENING_THRESHOLD", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [ + { + "name": "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", + "unique": true, + "columnNames": [ + "FOLDER_PATH", + "FOLDER_CLOUD_ID" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`FOLDER_PATH`, `FOLDER_CLOUD_ID`)" + } + ], + "foreignKeys": [ + { + "table": "CLOUD_ENTITY", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "FOLDER_CLOUD_ID" + ], + "referencedColumns": [ + "_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, 'ab8cd826759896443cfcac2f825a509c')" + ] + } +} \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index d7d5a7ec6..409ed2b42 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -1,5 +1,6 @@ package org.cryptomator.data.db +/* import android.content.Context import android.database.sqlite.SQLiteDatabase import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -644,3 +645,4 @@ class UpgradeDatabaseTest { Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent())) } } +*/ \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt new file mode 100644 index 000000000..4f0f2b5b4 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt @@ -0,0 +1,34 @@ +package org.cryptomator.data.db + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.cryptomator.data.db.entities.CloudEntity +import org.cryptomator.data.db.entities.VaultEntity + +@Dao +interface CloudDao { + + @Query("SELECT * FROM CLOUD_ENTITY WHERE _id = :id LIMIT 1") + fun load(id: Long): CloudEntity + + @Query("SELECT * from CLOUD_ENTITY") + fun loadAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun storeReplacing(entity: CloudEntity): RowId + + @Query("SELECT * FROM CLOUD_ENTITY WHERE rowid = :rowId") + fun loadFromRowId(rowId: RowId): CloudEntity + + @Transaction + fun storeReplacingAndReload(entity: CloudEntity): CloudEntity { + return loadFromRowId(storeReplacing(entity)) + } + + @Delete + fun delete(entity: CloudEntity) +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/CompoundDatabaseUpgrade.java b/data/src/main/java/org/cryptomator/data/db/CompoundDatabaseUpgrade.java deleted file mode 100755 index a4ab1f220..000000000 --- a/data/src/main/java/org/cryptomator/data/db/CompoundDatabaseUpgrade.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cryptomator.data.db; - -import java.util.List; - -class CompoundDatabaseUpgrade extends DatabaseUpgrade { - - private final List upgrades; - - public CompoundDatabaseUpgrade(List upgrades) { - super(upgrades.get(0).from(), upgrades.get(upgrades.size() - 1).to()); - this.upgrades = upgrades; - } - - @Override - protected void internalApplyTo(org.greenrobot.greendao.database.Database db, int origin) { - for (DatabaseUpgrade upgrade : upgrades) { - upgrade.applyTo(db, origin); - } - } - -} diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt new file mode 100644 index 000000000..74c3181eb --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -0,0 +1,21 @@ +package org.cryptomator.data.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import org.cryptomator.data.db.entities.CloudEntity +import org.cryptomator.data.db.entities.UpdateCheckEntity +import org.cryptomator.data.db.entities.VaultEntity + +@Database(version = 13, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class]) +abstract class CryptomatorDatabase : RoomDatabase() { + + abstract fun cloudDao(): CloudDao + + abstract fun updateCheckDao(): UpdateCheckDao + + abstract fun vaultDao(): VaultDao + + fun clearCache() { + //TODO + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/Database.java b/data/src/main/java/org/cryptomator/data/db/Database.java deleted file mode 100644 index 7f24da91d..000000000 --- a/data/src/main/java/org/cryptomator/data/db/Database.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.cryptomator.data.db; - -import org.cryptomator.data.db.entities.DaoMaster; -import org.cryptomator.data.db.entities.DaoSession; -import org.cryptomator.data.db.entities.DatabaseEntity; - -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton -public class Database { - - private final DaoSession daoSession; - - @Inject - public Database(DatabaseFactory databaseFactory) { - DaoMaster daoMaster = new DaoMaster(databaseFactory.getWritableDatabase()); - daoSession = daoMaster.newSession(); - } - - public T load(Class type, long id) { - return daoSession.load(type, id); - } - - public void delete(T entity) { - daoSession.delete(entity); - } - - public List loadAll(Class type) { - return daoSession.loadAll(type); - } - - public T create(T entity) { - long id = daoSession.insert(entity); - return load((Class) entity.getClass(), id); - } - - public T store(T entity) { - Long id = entity.getId(); - if (id == null) { - id = daoSession.insert(entity); - } else { - daoSession.update(entity); - } - return load((Class) entity.getClass(), id); - } - - public void clearCache() { - daoSession.clear(); - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseFactory.java b/data/src/main/java/org/cryptomator/data/db/DatabaseFactory.java deleted file mode 100644 index 36cea49ae..000000000 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.cryptomator.data.db; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; - -import org.cryptomator.data.db.entities.DaoMaster; -import org.greenrobot.greendao.database.Database; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import timber.log.Timber; - -import static org.cryptomator.data.db.entities.DaoMaster.SCHEMA_VERSION; - -@Singleton -class DatabaseFactory extends DaoMaster.OpenHelper { - - private static final String DATABASE_NAME = "Cryptomator"; - - private final DatabaseUpgrades databaseUpgrades; - - @Inject - public DatabaseFactory(Context context, DatabaseUpgrades databaseUpgrades) { - super(context, DATABASE_NAME); - this.databaseUpgrades = databaseUpgrades; - } - - @Override - public void onConfigure(SQLiteDatabase db) { - super.onConfigure(db); - - Timber.tag("Database").i("Configure v%d", db.getVersion()); - - if (!db.isReadOnly()) { - db.setForeignKeyConstraintsEnabled(true); - } - } - - @Override - public void onCreate(Database db) { - Timber.tag("Database").i("Create v%s", SCHEMA_VERSION); - databaseUpgrades.getUpgrade(0, SCHEMA_VERSION).applyTo(db, 0); - } - - @Override - public void onUpgrade(Database db, int oldVersion, int newVersion) { - Timber.tag("Database").i("Upgrade v" + oldVersion + " to v" + newVersion); - databaseUpgrades.getUpgrade(oldVersion, newVersion).applyTo(db, oldVersion); - } - - @Override - public void onOpen(SQLiteDatabase db) { - super.onOpen(db); - Timber.tag("Database").i("Open v%s", db.getVersion()); - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt new file mode 100644 index 000000000..8e5e796e4 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -0,0 +1,70 @@ +package org.cryptomator.data.db + +import android.content.Context +import androidx.room.Room +import androidx.room.migration.Migration +import org.cryptomator.data.db.migrations.Migration12To13 +import javax.inject.Singleton +import dagger.Module +import dagger.Provides + +@Module +class DatabaseModule { + + @Singleton + @Provides + fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { + return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // + .addMigrations(*migrations) // + .addMigrations(Migration12To13) // + .build() + } + + @Singleton + @Provides + fun provideCloudDao(database: CryptomatorDatabase): CloudDao { + return database.cloudDao() + } + + @Singleton + @Provides + fun provideUpdateCheckDao(database: CryptomatorDatabase): UpdateCheckDao { + return database.updateCheckDao() + } + + @Singleton + @Provides + fun provideVaultDao(database: CryptomatorDatabase): VaultDao { + return database.vaultDao() + } + + @Singleton + @Provides + internal fun provideMigrations( + upgrade0To1: Upgrade0To1, // + upgrade1To2: Upgrade1To2, // + upgrade2To3: Upgrade2To3, // + upgrade3To4: Upgrade3To4, // + upgrade4To5: Upgrade4To5, // + upgrade5To6: Upgrade5To6, // + upgrade6To7: Upgrade6To7, // + upgrade7To8: Upgrade7To8, // + upgrade8To9: Upgrade8To9, // + upgrade9To10: Upgrade9To10, // + upgrade10To11: Upgrade10To11, // + upgrade11To12: Upgrade11To12, // + ): Array = arrayOf( + upgrade0To1, + upgrade1To2, + upgrade2To3, + upgrade5To6, + upgrade3To4, + upgrade4To5, + upgrade6To7, + upgrade7To8, + upgrade8To9, + upgrade9To10, + upgrade10To11, + upgrade11To12, + ) +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrade.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrade.java deleted file mode 100644 index da2fb34df..000000000 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrade.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cryptomator.data.db; - -import org.greenrobot.greendao.database.Database; - -import timber.log.Timber; - -abstract class DatabaseUpgrade implements Comparable { - - private final int from; - private final int to; - - DatabaseUpgrade(int from, int to) { - this.from = from; - this.to = to; - } - - public int from() { - return from; - } - - public int to() { - return to; - } - - @Override - public int compareTo(DatabaseUpgrade other) { - int compareByFrom = from - other.from; - if (compareByFrom != 0) { - return compareByFrom; - } - return to - other.to; - } - - final void applyTo(Database db, int origin) { - Timber.tag("DatabaseUpgrade").i("Running %s (%d -> %d)", getClass().getSimpleName(), from, to); - internalApplyTo(db, origin); - } - - protected abstract void internalApplyTo(Database db, int origin); - -} diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java deleted file mode 100644 index 63566c5b8..000000000 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.cryptomator.data.db; - -import static java.lang.String.format; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton -class DatabaseUpgrades { - - private final Map> availableUpgrades; - - @Inject - public DatabaseUpgrades( // - Upgrade0To1 upgrade0To1, // - Upgrade1To2 upgrade1To2, // - Upgrade2To3 upgrade2To3, // - Upgrade3To4 upgrade3To4, // - Upgrade4To5 upgrade4To5, // - Upgrade5To6 upgrade5To6, // - Upgrade6To7 upgrade6To7, // - Upgrade7To8 upgrade7To8, // - Upgrade8To9 upgrade8To9, // - Upgrade9To10 upgrade9To10, // - Upgrade10To11 upgrade10To11, // - Upgrade11To12 upgrade11To12 - ) { - - availableUpgrades = defineUpgrades( // - upgrade0To1, // - upgrade1To2, // - upgrade2To3, // - upgrade3To4, // - upgrade4To5, // - upgrade5To6, // - upgrade6To7, // - upgrade7To8, // - upgrade8To9, // - upgrade9To10, // - upgrade10To11, // - upgrade11To12); - } - - private Map> defineUpgrades(DatabaseUpgrade... upgrades) { - Map> result = new HashMap<>(); - for (DatabaseUpgrade upgrade : upgrades) { - if (!result.containsKey(upgrade.from())) { - result.put(upgrade.from(), new ArrayList<>()); - } - result.get(upgrade.from()).add(upgrade); - } - for (List list : result.values()) { - Collections.sort(list, Comparator.reverseOrder()); - } - return result; - } - - public DatabaseUpgrade getUpgrade(int oldVersion, int newVersion) { - List upgrades = new ArrayList<>(10); - if (!findUpgrades(upgrades, oldVersion, newVersion)) { - throw new IllegalStateException(format("No upgrade path from %d to %d", oldVersion, newVersion)); - } - return new CompoundDatabaseUpgrade(upgrades); - } - - private boolean findUpgrades(List upgrades, int oldVersion, int newVersion) { - if (oldVersion == newVersion) { - return true; - } - - List upgradesFromOldVersion = availableUpgrades.get(oldVersion); - if (upgradesFromOldVersion == null) { - return false; - } - for (DatabaseUpgrade upgrade : upgradesFromOldVersion) { - if (upgrade.to() > newVersion) { - continue; - } - upgrades.add(upgrade); - if (findUpgrades(upgrades, upgrade.to(), newVersion)) { - return true; - } - upgrades.remove(upgrades.size() - 1); - } - return false; - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/RowId.kt b/data/src/main/java/org/cryptomator/data/db/RowId.kt new file mode 100644 index 000000000..d1429964d --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/RowId.kt @@ -0,0 +1,3 @@ +package org.cryptomator.data.db + +typealias RowId = Long \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/Sql.java b/data/src/main/java/org/cryptomator/data/db/Sql.java index 9c50cc4bc..2b510ec7a 100644 --- a/data/src/main/java/org/cryptomator/data/db/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/Sql.java @@ -1,21 +1,24 @@ package org.cryptomator.data.db; +import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; +import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; +import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; +import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; +import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.TEXT; +import static java.lang.String.format; + import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import org.greenrobot.greendao.database.Database; +import androidx.sqlite.db.SupportSQLiteDatabase; import java.util.ArrayList; import java.util.List; -import static java.lang.String.format; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.TEXT; - +//TODO Use precompiled statements for all? +//TODO Compatibility of Rename table with new androids +//https://developer.android.com/reference/android/database/sqlite/package-summary.html -- API 26 -> 3.18 class Sql { public static SqlInsertBuilder insertInto(String table) { @@ -85,10 +88,6 @@ public static ValueHolder toNull() { return (column, contentValues) -> contentValues.putNull(column); } - private static SQLiteDatabase unwrap(Database wrapped) { - return (SQLiteDatabase) wrapped.getRawDatabase(); - } - public interface ValueHolder { void put(String column, ContentValues contentValues); @@ -107,9 +106,6 @@ public static class SqlQueryBuilder { private final List whereArgs = new ArrayList<>(); private List columns = new ArrayList<>(); - private String groupBy; - private String having; - private String limit; public SqlQueryBuilder(String tableName) { this.tableName = tableName; @@ -128,26 +124,20 @@ public SqlQueryBuilder where(String column, Criterion criterion) { return this; } - public SqlQueryBuilder groupBy(String groupBy) { - this.groupBy = groupBy; - return this; - } - - public SqlQueryBuilder having(String having) { - this.having = having; - return this; - } + public Cursor executeOn(SupportSQLiteDatabase db) { + if(columns == null || columns.isEmpty()) { + throw new IllegalArgumentException(); + } + if(tableName == null || tableName.trim().isEmpty()) { + throw new IllegalArgumentException(); + } - public SqlQueryBuilder limit(String limit) { - this.limit = limit; - return this; - } + StringBuilder query = new StringBuilder().append("SELECT ("); + appendColumns(query, columns.toArray(new String[0]), null, false); + query.append(") FROM ").append('"').append(tableName).append('"').append(" WHERE ").append(whereClause); - public Cursor executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); - return db.query(tableName, columns.toArray(new String[columns.size()]), whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()]), groupBy, having, limit); + return db.query(query.toString(), whereArgs.toArray()); } - } public static class SqlUpdateBuilder { @@ -175,12 +165,13 @@ public SqlUpdateBuilder where(String column, Criterion criterion) { return this; } - public void executeOn(Database wrapped) { + public void executeOn(SupportSQLiteDatabase db) { if (contentValues.size() == 0) { throw new IllegalStateException("At least one value must be set"); } - SQLiteDatabase db = unwrap(wrapped); - db.update(tableName, contentValues, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); + //TODO Error handling in replacement of SQLiteDatabase#insertOrThrow, all-null-handling + // ... Handling of everything that was changed since the prior implementation + db.update(tableName, SQLiteDatabase.CONFLICT_FAIL, contentValues, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); } } @@ -193,8 +184,7 @@ private SqlDropIndexBuilder(String index) { this.index = index; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.execSQL(format("DROP INDEX \"%s\"", index)); } @@ -223,8 +213,7 @@ public SqlUniqueIndexBuilder asc(String column) { return this; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.execSQL(format("CREATE UNIQUE INDEX \"%s\" ON \"%s\" (%s)", indexName, table, columns)); } } @@ -237,8 +226,7 @@ private SqlDropTableBuilder(String table) { this.table = table; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.execSQL(format("DROP TABLE \"%s\"", table)); } @@ -258,15 +246,13 @@ public SqlAlterTableBuilder renameTo(String newName) { return this; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.execSQL(format("ALTER TABLE \"%s\" RENAME TO \"%s\"", table, newName)); } } public static class SqlInsertSelectBuilder { - private static final int NOT_FOUND = -1; private final String table; private final String[] selectedColumns; private final StringBuilder joinClauses = new StringBuilder(); @@ -284,36 +270,16 @@ public SqlInsertSelectBuilder from(String sourceTableName) { return this; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { StringBuilder query = new StringBuilder().append("INSERT INTO \"").append(table).append("\" ("); - appendColumns(query, columns, false); + appendColumns(query, columns, sourceTableName, false); query.append(") SELECT "); - appendColumns(query, selectedColumns, true); + appendColumns(query, selectedColumns, sourceTableName, true); query.append(" FROM \"").append(sourceTableName).append('"'); query.append(joinClauses); db.execSQL(query.toString()); } - private void appendColumns(StringBuilder query, String[] columns, boolean appendSourceTableName) { - boolean notFirst = false; - - for (String column : columns) { - if (notFirst) { - query.append(','); - } - - if (appendSourceTableName && column.indexOf('.') == NOT_FOUND) { - query.append('"').append(sourceTableName).append("\".\"").append(column).append('"'); - } else { - column = column.replace(".", "\".\""); - query.append('"').append(column).append('"'); - } - - notFirst = true; - } - } - public SqlInsertSelectBuilder join(String targetTable, String sourceColumn) { sourceColumn = sourceColumn.replace(".", "\".\""); joinClauses.append(" JOIN \"") // @@ -396,8 +362,7 @@ public SqlCreateTableBuilder column(String name, ColumnType type, ColumnConstrai return this; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.execSQL(format("CREATE TABLE \"%s\" (%s%s)", table, columns, foreignKeys)); } @@ -492,9 +457,10 @@ public SqlInsertBuilder bool(String column, Boolean value) { return this; } - public Long executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); - return db.insertOrThrow(table, null, contentValues); + public Long executeOn(SupportSQLiteDatabase db) { + //TODO Error handling in replacement of SQLiteDatabase#insertOrThrow, all-null-handling + // ... Handling of everything that was changed since the prior implementation + return db.insert(this.table, SQLiteDatabase.CONFLICT_FAIL, contentValues); } } @@ -517,10 +483,29 @@ public SqlDeleteBuilder where(String column, Criterion criterion) { return this; } - public void executeOn(Database wrapped) { - SQLiteDatabase db = unwrap(wrapped); + public void executeOn(SupportSQLiteDatabase db) { db.delete(tableName, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); } } + private static final int NOT_FOUND = -1; + private static void appendColumns(StringBuilder query, String[] columns, String sourceTableName, boolean appendSourceTableName) { + boolean notFirst = false; + + for (String column : columns) { + if (notFirst) { + query.append(','); + } + + if (appendSourceTableName && column.indexOf('.') == NOT_FOUND) { + query.append('"').append(sourceTableName).append("\".\"").append(column).append('"'); + } else { + column = column.replace(".", "\".\""); + query.append('"').append(column).append('"'); + } + + notFirst = true; + } + } + } diff --git a/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt new file mode 100644 index 000000000..623cf52b7 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt @@ -0,0 +1,34 @@ +package org.cryptomator.data.db + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.cryptomator.data.db.entities.UpdateCheckEntity + +@Dao +interface UpdateCheckDao { + + @Query("SELECT * FROM UPDATE_CHECK_ENTITY WHERE _id = :id LIMIT 1") + fun load(id: Long): UpdateCheckEntity + + @Query("SELECT * from UPDATE_CHECK_ENTITY") + fun loadAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun storeReplacing(entity: UpdateCheckEntity): RowId + + @Query("SELECT * FROM UPDATE_CHECK_ENTITY WHERE rowid = :rowId") + fun loadFromRowId(rowId: RowId): UpdateCheckEntity + + @Transaction + fun storeReplacingAndReload(entity: UpdateCheckEntity): UpdateCheckEntity { + return loadFromRowId(storeReplacing(entity)) + } + + @Delete + fun delete(entity: UpdateCheckEntity) + +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt index a8f7849fb..bbb1ecb21 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt @@ -1,15 +1,16 @@ package org.cryptomator.data.db +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour import org.cryptomator.domain.CloudType -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { +internal class Upgrade0To1 @Inject constructor() : Migration(0, 1) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { createCloudEntityTable(db) createVaultEntityTable(db) createDropboxCloud(db) @@ -18,7 +19,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { createOnedriveCloud(db) } - private fun createCloudEntityTable(db: Database) { + private fun createCloudEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("CLOUD_ENTITY") // .id() // .requiredText("TYPE") // @@ -29,7 +30,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { .executeOn(db) } - private fun createVaultEntityTable(db: Database) { + private fun createVaultEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("VAULT_ENTITY") // .id() // .optionalInt("FOLDER_CLOUD_ID") // @@ -46,7 +47,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { .executeOn(db) } - private fun createDropboxCloud(db: Database) { + private fun createDropboxCloud(db: SupportSQLiteDatabase) { Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 1) // .text("TYPE", CloudType.DROPBOX.name) // @@ -57,7 +58,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { .executeOn(db) } - private fun createGoogleDriveCloud(db: Database) { + private fun createGoogleDriveCloud(db: SupportSQLiteDatabase) { Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 2) // .text("TYPE", CloudType.GOOGLE_DRIVE.name) // @@ -68,7 +69,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { .executeOn(db) } - private fun createOnedriveCloud(db: Database) { + private fun createOnedriveCloud(db: SupportSQLiteDatabase) { Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 3) // .text("TYPE", CloudType.ONEDRIVE.name) // @@ -79,7 +80,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseUpgrade(0, 1) { .executeOn(db) } - private fun createLocalStorageCloud(db: Database) { + private fun createLocalStorageCloud(db: SupportSQLiteDatabase) { Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 4) // .text("TYPE", CloudType.LOCAL.name) // diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt index 263f18fd3..3b5d06e5d 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt @@ -1,16 +1,18 @@ package org.cryptomator.data.db -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade10To11 @Inject constructor() : DatabaseUpgrade(10, 11) { +internal class Upgrade10To11 @Inject constructor() : Migration(10, 11) { + private val defaultThreshold = 220 private val defaultVaultFormat = 8 private val onedriveCloudId = 3L - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { addFormatAndShorteningToDbEntity(db) @@ -24,7 +26,7 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseUpgrade(10, 11) { } } - private fun addFormatAndShorteningToDbEntity(db: Database) { + private fun addFormatAndShorteningToDbEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // .id() // @@ -58,14 +60,14 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseUpgrade(10, 11) { } - private fun addDefaultFormatAndShorteningThresholdToVaults(db: Database) { + private fun addDefaultFormatAndShorteningThresholdToVaults(db: SupportSQLiteDatabase) { Sql.update("VAULT_ENTITY") .set("FORMAT", Sql.toInteger(defaultVaultFormat)) .set("SHORTENING_THRESHOLD", Sql.toInteger(defaultThreshold)) .executeOn(db) } - private fun deleteOnedriveCloudIfNotSetUp(db: Database) { + private fun deleteOnedriveCloudIfNotSetUp(db: SupportSQLiteDatabase) { Sql.deleteFrom("CLOUD_ENTITY") .where("_id", Sql.eq(onedriveCloudId)) .where("TYPE", Sql.eq("ONEDRIVE")) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt index 2ec846443..181b4688d 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt @@ -1,15 +1,16 @@ package org.cryptomator.data.db +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.google.common.base.Optional import org.cryptomator.util.SharedPreferencesHandler -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade11To12 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseUpgrade(11, 12) { +internal class Upgrade11To12 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(11, 12) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { when (sharedPreferencesHandler.updateIntervalInDays()) { Optional.of(7), Optional.of(30) -> sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(1)) } diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt index b4541bfef..ca3f0552b 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt @@ -1,18 +1,19 @@ package org.cryptomator.data.db -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade1To2 @Inject constructor() : DatabaseUpgrade(1, 2) { +internal class Upgrade1To2 @Inject constructor() : Migration(1, 2) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { createUpdateCheckTable(db) createInitialUpdateStatus(db) } - private fun createUpdateCheckTable(db: Database) { + private fun createUpdateCheckTable(db: SupportSQLiteDatabase) { db.beginTransaction() try { Sql.createTable("UPDATE_CHECK_ENTITY") // @@ -29,7 +30,7 @@ internal class Upgrade1To2 @Inject constructor() : DatabaseUpgrade(1, 2) { } } - private fun createInitialUpdateStatus(db: Database) { + private fun createInitialUpdateStatus(db: SupportSQLiteDatabase) { Sql.insertInto("UPDATE_CHECK_ENTITY") // .integer("_id", 1) // .text("LICENSE_TOKEN", null) // diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt index 7938f5d27..dfe9ef86e 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt @@ -2,15 +2,16 @@ package org.cryptomator.data.db import android.content.Context import android.content.SharedPreferences +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.util.crypto.CredentialCryptor -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseUpgrade(2, 3) { +internal class Upgrade2To3 @Inject constructor(private val context: Context) : Migration(2, 3) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { Sql.query("CLOUD_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt index fd443ec8f..f801beb61 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt @@ -1,14 +1,15 @@ package org.cryptomator.data.db +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade3To4 @Inject constructor() : DatabaseUpgrade(3, 4) { +internal class Upgrade3To4 @Inject constructor() : Migration(3, 4) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { addPositionToVaultSchema(db) @@ -19,7 +20,7 @@ internal class Upgrade3To4 @Inject constructor() : DatabaseUpgrade(3, 4) { } } - private fun addPositionToVaultSchema(db: Database) { + private fun addPositionToVaultSchema(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // .id() // @@ -50,7 +51,7 @@ internal class Upgrade3To4 @Inject constructor() : DatabaseUpgrade(3, 4) { Sql.dropTable("VAULT_ENTITY_OLD").executeOn(db) } - private fun initVaultPositionUsingCurrentSortOrder(db: Database) { + private fun initVaultPositionUsingCurrentSortOrder(db: SupportSQLiteDatabase) { Sql.query("VAULT_ENTITY").executeOn(db).use { while (it.moveToNext()) { Sql.update("VAULT_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt index 84baee330..6e853998e 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt @@ -1,13 +1,14 @@ package org.cryptomator.data.db -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) { +internal class Upgrade4To5 @Inject constructor() : Migration(4, 5) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeWebdavUrlInCloudEntityToUrl(db) @@ -17,7 +18,7 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) { } } - private fun changeWebdavUrlInCloudEntityToUrl(db: Database) { + private fun changeWebdavUrlInCloudEntityToUrl(db: SupportSQLiteDatabase) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // @@ -40,7 +41,7 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) { Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db) } - private fun recreateVaultEntity(db: Database) { + private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // .id() // diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt index 75fb443a0..de48c8d41 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt @@ -1,13 +1,14 @@ package org.cryptomator.data.db -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade5To6 @Inject constructor() : DatabaseUpgrade(5, 6) { +internal class Upgrade5To6 @Inject constructor() : Migration(5, 6) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeCloudEntityToSupportS3(db) @@ -17,7 +18,7 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseUpgrade(5, 6) { } } - private fun changeCloudEntityToSupportS3(db: Database) { + private fun changeCloudEntityToSupportS3(db: SupportSQLiteDatabase) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // @@ -43,7 +44,7 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseUpgrade(5, 6) { Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db) } - private fun recreateVaultEntity(db: Database) { + private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // .id() // diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt index 8e625516b..9f761255b 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt @@ -1,15 +1,16 @@ package org.cryptomator.data.db import android.database.sqlite.SQLiteException -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton import timber.log.Timber @Singleton -internal class Upgrade6To7 @Inject constructor() : DatabaseUpgrade(6, 7) { +internal class Upgrade6To7 @Inject constructor() : Migration(6, 7) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeUpdateEntityToSupportSha256Verification(db) @@ -19,7 +20,7 @@ internal class Upgrade6To7 @Inject constructor() : DatabaseUpgrade(6, 7) { } } - private fun changeUpdateEntityToSupportSha256Verification(db: Database) { + private fun changeUpdateEntityToSupportSha256Verification(db: SupportSQLiteDatabase) { Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) Sql.createTable("UPDATE_CHECK_ENTITY") // @@ -46,7 +47,7 @@ internal class Upgrade6To7 @Inject constructor() : DatabaseUpgrade(6, 7) { Sql.dropTable("UPDATE_CHECK_ENTITY_OLD").executeOn(db) } - fun tryToRecoverFromSQLiteException(db: Database) { + fun tryToRecoverFromSQLiteException(db: SupportSQLiteDatabase) { var licenseToken: String? = null try { diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt index 5f1a65cc1..986614f37 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt @@ -1,13 +1,14 @@ package org.cryptomator.data.db -import org.greenrobot.greendao.database.Database +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade7To8 @Inject constructor() : DatabaseUpgrade(7, 8) { +internal class Upgrade7To8 @Inject constructor() : Migration(7, 8) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { dropS3Vaults(db) @@ -18,13 +19,13 @@ internal class Upgrade7To8 @Inject constructor() : DatabaseUpgrade(7, 8) { } } - private fun dropS3Vaults(db: Database) { + private fun dropS3Vaults(db: SupportSQLiteDatabase) { Sql.deleteFrom("VAULT_ENTITY") // .where("CLOUD_TYPE", Sql.eq("S3")) .executeOn(db) } - private fun dropS3Clouds(db: Database) { + private fun dropS3Clouds(db: SupportSQLiteDatabase) { Sql.deleteFrom("CLOUD_ENTITY") // .where("TYPE", Sql.eq("S3")) .executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt index a3fa283bc..82d9a9af1 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt @@ -1,14 +1,15 @@ package org.cryptomator.data.db +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.util.SharedPreferencesHandler -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade8To9 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseUpgrade(8, 9) { +internal class Upgrade8To9 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(8, 9) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { // toggle beta screen dialog already shown to display it again in this beta sharedPreferencesHandler.setBetaScreenDialogAlreadyShown(false) } diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt index 01cf79fa5..44de8f62b 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt @@ -1,18 +1,19 @@ package org.cryptomator.data.db +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton import timber.log.Timber @Singleton -internal class Upgrade9To10 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseUpgrade(9, 10) { +internal class Upgrade9To10 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(9, 10) { private val defaultLocalStorageCloudId = 4L - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrate(db: SupportSQLiteDatabase) { db.beginTransaction() try { diff --git a/data/src/main/java/org/cryptomator/data/db/VaultDao.kt b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt new file mode 100644 index 000000000..46c93e6a1 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt @@ -0,0 +1,33 @@ +package org.cryptomator.data.db + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import org.cryptomator.data.db.entities.VaultEntity + +@Dao +interface VaultDao { + + @Query("SELECT * FROM VAULT_ENTITY WHERE _id = :id LIMIT 1") + fun load(id: Long): VaultEntity + + @Query("SELECT * from VAULT_ENTITY") + fun loadAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun storeReplacing(entity: VaultEntity): RowId + + @Query("SELECT * FROM VAULT_ENTITY WHERE rowid = :rowId") + fun loadFromRowId(rowId: RowId): VaultEntity + + @Transaction + fun storeReplacingAndReload(entity: VaultEntity): VaultEntity { + return loadFromRowId(storeReplacing(entity)) + } + + @Delete + fun delete(entity: VaultEntity) +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java deleted file mode 100644 index b939e9251..000000000 --- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.cryptomator.data.db.entities; - -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; -import org.greenrobot.greendao.annotation.NotNull; - -@Entity -public class CloudEntity extends DatabaseEntity { - - @Id - private Long id; - - @NotNull - private String type; - - private String accessToken; - - private String url; - - private String username; - - private String webdavCertificate; - - private String s3Bucket; - - private String s3Region; - - private String s3SecretKey; - - @Generated(hash = 1685351705) - public CloudEntity(Long id, @NotNull String type, String accessToken, String url, String username, String webdavCertificate, String s3Bucket, String s3Region, String s3SecretKey) { - this.id = id; - this.type = type; - this.accessToken = accessToken; - this.url = url; - this.username = username; - this.webdavCertificate = webdavCertificate; - this.s3Bucket = s3Bucket; - this.s3Region = s3Region; - this.s3SecretKey = s3SecretKey; - } - - @Generated(hash = 1354152224) - public CloudEntity() { - } - - public String getAccessToken() { - return this.accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public String getType() { - return this.type; - } - - public void setType(String type) { - this.type = type; - } - - public Long getId() { - return this.id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getWebdavCertificate() { - return webdavCertificate; - } - - public void setWebdavCertificate(String webdavCertificate) { - this.webdavCertificate = webdavCertificate; - } - - public String getS3Bucket() { - return this.s3Bucket; - } - - public void setS3Bucket(String s3Bucket) { - this.s3Bucket = s3Bucket; - } - - public String getS3Region() { - return this.s3Region; - } - - public void setS3Region(String s3Region) { - this.s3Region = s3Region; - } - - public String getS3SecretKey() { - return this.s3SecretKey; - } - - public void setS3SecretKey(String s3SecretKey) { - this.s3SecretKey = s3SecretKey; - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt new file mode 100644 index 000000000..82dc09c9a --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt @@ -0,0 +1,20 @@ +package org.cryptomator.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "CLOUD_ENTITY") +data class CloudEntity @JvmOverloads constructor( + //TODO Remove @JvmOverloads + //TODO Nullability + @PrimaryKey @ColumnInfo("_id") override var id: Long?, + @ColumnInfo("TYPE") var type: String, + @ColumnInfo("ACCESS_TOKEN") var accessToken: String? = null, + @ColumnInfo("URL") var url: String? = null, + @ColumnInfo("USERNAME") var username: String? = null, + @ColumnInfo("WEBDAV_CERTIFICATE") var webdavCertificate: String? = null, + @ColumnInfo("S3_BUCKET") var s3Bucket: String? = null, + @ColumnInfo("S3_REGION") var s3Region: String? = null, + @ColumnInfo("S3_SECRET_KEY") var s3SecretKey: String? = null, +) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.java deleted file mode 100755 index c40978e34..000000000 --- a/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.cryptomator.data.db.entities; - -public abstract class DatabaseEntity { - - DatabaseEntity() { - } - - public abstract Long getId(); - -} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.kt new file mode 100644 index 000000000..e02330d4f --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/entities/DatabaseEntity.kt @@ -0,0 +1,6 @@ +package org.cryptomator.data.db.entities + +interface DatabaseEntity { + + val id: Long? +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.java deleted file mode 100644 index aec5f36bc..000000000 --- a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.cryptomator.data.db.entities; - -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; - -@Entity -public class UpdateCheckEntity extends DatabaseEntity { - - @Id - private Long id; - - private String licenseToken; - - private String releaseNote; - - private String version; - - private String urlToApk; - - private String apkSha256; - - private String urlToReleaseNote; - - public UpdateCheckEntity() { - } - - @Generated(hash = 67239496) - public UpdateCheckEntity(Long id, String licenseToken, String releaseNote, String version, String urlToApk, String apkSha256, String urlToReleaseNote) { - this.id = id; - this.licenseToken = licenseToken; - this.releaseNote = releaseNote; - this.version = version; - this.urlToApk = urlToApk; - this.apkSha256 = apkSha256; - this.urlToReleaseNote = urlToReleaseNote; - } - - @Override - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getLicenseToken() { - return this.licenseToken; - } - - public void setLicenseToken(String licenseToken) { - this.licenseToken = licenseToken; - } - - public String getVersion() { - return this.version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getUrlToApk() { - return this.urlToApk; - } - - public void setUrlToApk(String urlToApk) { - this.urlToApk = urlToApk; - } - - public String getReleaseNote() { - return this.releaseNote; - } - - public void setReleaseNote(String releaseNote) { - this.releaseNote = releaseNote; - } - - public String getUrlToReleaseNote() { - return this.urlToReleaseNote; - } - - public void setUrlToReleaseNote(String urlToReleaseNote) { - this.urlToReleaseNote = urlToReleaseNote; - } - - public String getApkSha256() { - return this.apkSha256; - } - - public void setApkSha256(String apkSha256) { - this.apkSha256 = apkSha256; - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt new file mode 100644 index 000000000..cd53cef9d --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt @@ -0,0 +1,17 @@ +package org.cryptomator.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "UPDATE_CHECK_ENTITY") +data class UpdateCheckEntity @JvmOverloads constructor( + //TODO Remove @JvmOverloads + @PrimaryKey @ColumnInfo("_id") override var id: Long?, + @ColumnInfo("LICENSE_TOKEN") var licenseToken: String?, + @ColumnInfo("RELEASE_NOTE") var releaseNote: String?, + @ColumnInfo("VERSION") var version: String?, + @ColumnInfo("URL_TO_APK") var urlToApk: String?, + @ColumnInfo("APK_SHA256") var apkSha256: String?, + @ColumnInfo("URL_TO_RELEASE_NOTE") var urlToReleaseNote: String?, +) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java deleted file mode 100644 index 9f384b3c6..000000000 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java +++ /dev/null @@ -1,215 +0,0 @@ -package org.cryptomator.data.db.entities; - -import org.greenrobot.greendao.DaoException; -import org.greenrobot.greendao.annotation.Entity; -import org.greenrobot.greendao.annotation.Generated; -import org.greenrobot.greendao.annotation.Id; -import org.greenrobot.greendao.annotation.Index; -import org.greenrobot.greendao.annotation.NotNull; -import org.greenrobot.greendao.annotation.ToOne; - -@Entity(indexes = {@Index(value = "folderPath,folderCloudId", unique = true)}) -public class VaultEntity extends DatabaseEntity { - - @Id - private Long id; - - private Long folderCloudId; - - @ToOne(joinProperty = "folderCloudId") - private CloudEntity folderCloud; - - private String folderPath; - - private String folderName; - - @NotNull - private String cloudType; - - private String password; - - private Integer position; - - private Integer format; - - private Integer shorteningThreshold; - - /** - * Used for active entity operations. - */ - @Generated(hash = 941685503) - private transient VaultEntityDao myDao; - /** - * Used to resolve relations - */ - @Generated(hash = 2040040024) - private transient DaoSession daoSession; - @Generated(hash = 229273163) - private transient Long folderCloud__resolvedKey; - - @Generated(hash = 530735379) - public VaultEntity(Long id, Long folderCloudId, String folderPath, String folderName, @NotNull String cloudType, String password, Integer position, Integer format, Integer shorteningThreshold) { - this.id = id; - this.folderCloudId = folderCloudId; - this.folderPath = folderPath; - this.folderName = folderName; - this.cloudType = cloudType; - this.password = password; - this.position = position; - this.format = format; - this.shorteningThreshold = shorteningThreshold; - } - - @Generated(hash = 691253864) - public VaultEntity() { - } - - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 1942392019) - public void refresh() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.refresh(this); - } - - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 713229351) - public void update() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.update(this); - } - - /** - * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. - * Entity must attached to an entity context. - */ - @Generated(hash = 128553479) - public void delete() { - if (myDao == null) { - throw new DaoException("Entity is detached from DAO context"); - } - myDao.delete(this); - } - - /** - * To-one relationship, resolved on first access. - */ - @Generated(hash = 1508817413) - public CloudEntity getFolderCloud() { - Long __key = this.folderCloudId; - if (folderCloud__resolvedKey == null || !folderCloud__resolvedKey.equals(__key)) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - CloudEntityDao targetDao = daoSession.getCloudEntityDao(); - CloudEntity folderCloudNew = targetDao.load(__key); - synchronized (this) { - folderCloud = folderCloudNew; - folderCloud__resolvedKey = __key; - } - } - return folderCloud; - } - - /** - * called by internal mechanisms, do not call yourself. - */ - @Generated(hash = 1482096330) - public void setFolderCloud(CloudEntity folderCloud) { - synchronized (this) { - this.folderCloud = folderCloud; - folderCloudId = folderCloud == null ? null : folderCloud.getId(); - folderCloud__resolvedKey = folderCloudId; - } - } - - public String getFolderPath() { - return this.folderPath; - } - - public void setFolderPath(String folderPath) { - this.folderPath = folderPath; - } - - public String getFolderName() { - return folderName; - } - - public void setFolderName(String folderName) { - this.folderName = folderName; - } - - public Long getId() { - return this.id; - } - - public void setId(Long id) { - this.id = id; - } - - public Long getFolderCloudId() { - return this.folderCloudId; - } - - public void setFolderCloudId(Long folderCloudId) { - this.folderCloudId = folderCloudId; - } - - public String getCloudType() { - return this.cloudType; - } - - public void setCloudType(String cloudType) { - this.cloudType = cloudType; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public Integer getPosition() { - return this.position; - } - - public void setPosition(Integer position) { - this.position = position; - } - - public Integer getFormat() { - return this.format; - } - - public void setFormat(Integer format) { - this.format = format; - } - - public Integer getShorteningThreshold() { - return this.shorteningThreshold; - } - - public void setShorteningThreshold(Integer shorteningThreshold) { - this.shorteningThreshold = shorteningThreshold; - } - - /** called by internal mechanisms, do not call yourself. */ - @Generated(hash = 674742652) - public void __setDaoSession(DaoSession daoSession) { - this.daoSession = daoSession; - myDao = daoSession != null ? daoSession.getVaultEntityDao() : null; - } - -} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt new file mode 100644 index 000000000..e99b4ead6 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -0,0 +1,24 @@ +package org.cryptomator.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "VAULT_ENTITY", // + indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["FOLDER_PATH", "FOLDER_CLOUD_ID"], unique = true)], // + foreignKeys = [ForeignKey(CloudEntity::class, ["_id"], ["FOLDER_CLOUD_ID"], onDelete = ForeignKey.SET_NULL)] +) +data class VaultEntity constructor( + @PrimaryKey @ColumnInfo("_id") override val id: Long?, + @ColumnInfo("FOLDER_CLOUD_ID") val folderCloudId: Long?, //TODO Map to CloudEntity + @ColumnInfo("FOLDER_PATH") val folderPath: String?, + @ColumnInfo("FOLDER_NAME") val folderName: String?, + @ColumnInfo("CLOUD_TYPE") val cloudType: String, + @ColumnInfo("PASSWORD") val password: String?, + @ColumnInfo("POSITION") val position: Int?, + @ColumnInfo("FORMAT") val format: Int?, + @ColumnInfo("SHORTENING_THRESHOLD") val shorteningThreshold: Int?, +) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index 70e049f7a..d0a26ac7f 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -87,9 +87,7 @@ public Cloud fromEntity(CloudEntity entity) { @Override public CloudEntity toEntity(Cloud domainObject) { - CloudEntity result = new CloudEntity(); - result.setId(domainObject.id()); - result.setType(domainObject.type().name()); + CloudEntity result = new CloudEntity(domainObject.id(), domainObject.type().name()); switch (domainObject.type()) { case DROPBOX: result.setAccessToken(((DropboxCloud) domainObject).accessToken()); diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java index 2659922d7..6bc91cd90 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java @@ -1,5 +1,8 @@ package org.cryptomator.data.db.mappers; +import static org.cryptomator.domain.Vault.aVault; + +import org.cryptomator.data.db.CloudDao; import org.cryptomator.data.db.entities.VaultEntity; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudType; @@ -9,15 +12,15 @@ import javax.inject.Inject; import javax.inject.Singleton; -import static org.cryptomator.domain.Vault.aVault; - @Singleton public class VaultEntityMapper extends EntityMapper { private final CloudEntityMapper cloudEntityMapper; + private final CloudDao cloudDao; @Inject - public VaultEntityMapper(CloudEntityMapper cloudEntityMapper) { + public VaultEntityMapper(CloudEntityMapper cloudEntityMapper, CloudDao cloudDao) { + this.cloudDao = cloudDao; this.cloudEntityMapper = cloudEntityMapper; } @@ -37,26 +40,27 @@ public Vault fromEntity(VaultEntity entity) throws BackendException { } private Cloud cloudFrom(VaultEntity entity) { - if (entity.getFolderCloud() == null) { + if (entity.getFolderCloudId() == null) { return null; } - return cloudEntityMapper.fromEntity(entity.getFolderCloud()); + return cloudEntityMapper.fromEntity(cloudDao.load(entity.getFolderCloudId())); } @Override public VaultEntity toEntity(Vault domainObject) { - VaultEntity entity = new VaultEntity(); - entity.setId(domainObject.getId()); - entity.setFolderPath(domainObject.getPath()); - entity.setFolderName(domainObject.getName()); + Long folderCloudId = null; if (domainObject.getCloud() != null) { - entity.setFolderCloud(cloudEntityMapper.toEntity(domainObject.getCloud())); + folderCloudId = cloudEntityMapper.toEntity(domainObject.getCloud()).getId(); } - entity.setCloudType(domainObject.getCloudType().name()); - entity.setPassword(domainObject.getPassword()); - entity.setPosition(domainObject.getPosition()); - entity.setFormat(domainObject.getFormat()); - entity.setShorteningThreshold(domainObject.getShorteningThreshold()); - return entity; + return new VaultEntity(domainObject.getId(), // + folderCloudId, // + domainObject.getPath(), // + domainObject.getName(), // + domainObject.getCloudType().name(), // + domainObject.getPassword(), // + domainObject.getPosition(), // + domainObject.getFormat(), // + domainObject.getShorteningThreshold() // + ); } } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt new file mode 100644 index 000000000..5ebdb237b --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt @@ -0,0 +1,12 @@ +package org.cryptomator.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +//TODO Verify if Metadata from earlier tables was lost +object Migration12To13 : Migration(12, 13) { + + override fun migrate(database: SupportSQLiteDatabase) { + //NO-OP + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index f08137f2f..1f6b9e0ae 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -3,8 +3,7 @@ import com.google.common.base.Optional; import org.cryptomator.data.cloud.crypto.CryptoCloudFactory; -import org.cryptomator.data.db.Database; -import org.cryptomator.data.db.entities.CloudEntity; +import org.cryptomator.data.db.CryptomatorDatabase; import org.cryptomator.data.db.mappers.CloudEntityMapper; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudFolder; @@ -25,7 +24,7 @@ @Singleton class CloudRepositoryImpl implements CloudRepository { - private final Database database; + private final CryptomatorDatabase database; private final CryptoCloudFactory cryptoCloudFactory; private final CloudEntityMapper mapper; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -33,7 +32,7 @@ class CloudRepositoryImpl implements CloudRepository { @Inject public CloudRepositoryImpl(CloudEntityMapper mapper, // CryptoCloudFactory cryptoCloudFactory, // - Database database, // + CryptomatorDatabase database, // DispatchingCloudContentRepository dispatchingCloudContentRepository) { this.database = database; this.cryptoCloudFactory = cryptoCloudFactory; @@ -44,7 +43,7 @@ public CloudRepositoryImpl(CloudEntityMapper mapper, // @Override public List clouds(CloudType cloudType) throws BackendException { List cloudsFromType = new ArrayList<>(); - List allClouds = mapper.fromEntities(database.loadAll(CloudEntity.class)); + List allClouds = mapper.fromEntities(database.cloudDao().loadAll()); for (Cloud cloud : allClouds) { if (cloud.type().equals(cloudType)) { @@ -57,7 +56,7 @@ public List clouds(CloudType cloudType) throws BackendException { @Override public List allClouds() throws BackendException { - return mapper.fromEntities(database.loadAll(CloudEntity.class)); + return mapper.fromEntities(database.cloudDao().loadAll()); } @Override @@ -66,7 +65,7 @@ public Cloud store(Cloud cloud) { throw new IllegalArgumentException("Can not store non persistent cloud"); } - Cloud storedCloud = mapper.fromEntity(database.store(mapper.toEntity(cloud))); + Cloud storedCloud = mapper.fromEntity(database.cloudDao().storeReplacingAndReload(mapper.toEntity(cloud))); database.clearCache(); dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud); @@ -79,7 +78,7 @@ public void delete(Cloud cloud) { if (!cloud.persistent()) { throw new IllegalArgumentException("Can not delete non persistent cloud"); } - database.delete(mapper.toEntity(cloud)); + database.cloudDao().delete(mapper.toEntity(cloud)); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cloud); } diff --git a/data/src/main/java/org/cryptomator/data/repository/RepositoryModule.java b/data/src/main/java/org/cryptomator/data/repository/RepositoryModule.java index 7bd1fff98..867088299 100644 --- a/data/src/main/java/org/cryptomator/data/repository/RepositoryModule.java +++ b/data/src/main/java/org/cryptomator/data/repository/RepositoryModule.java @@ -1,5 +1,6 @@ package org.cryptomator.data.repository; +import org.cryptomator.data.db.DatabaseModule; import org.cryptomator.domain.repository.CloudContentRepository; import org.cryptomator.domain.repository.CloudRepository; import org.cryptomator.domain.repository.UpdateCheckRepository; @@ -10,7 +11,7 @@ import dagger.Module; import dagger.Provides; -@Module +@Module(includes = {DatabaseModule.class}) public class RepositoryModule { @Singleton diff --git a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java index 0d0018996..28efa8d39 100644 --- a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java @@ -7,7 +7,7 @@ import com.google.common.io.BaseEncoding; import org.apache.commons.codec.binary.Hex; -import org.cryptomator.data.db.Database; +import org.cryptomator.data.db.UpdateCheckDao; import org.cryptomator.data.db.entities.UpdateCheckEntity; import org.cryptomator.data.util.UserAgentInterceptor; import org.cryptomator.domain.exception.BackendException; @@ -46,14 +46,14 @@ public class UpdateCheckRepositoryImpl implements UpdateCheckRepository { private static final String HOSTNAME_LATEST_VERSION = "https://api.cryptomator.org/android/latest-version.json"; - private final Database database; + private final UpdateCheckDao updateCheckDao; private final OkHttpClient httpClient; private final Context context; @Inject - UpdateCheckRepositoryImpl(Database database, Context context) { + UpdateCheckRepositoryImpl(UpdateCheckDao updateCheckDao, Context context) { this.httpClient = httpClient(); - this.database = database; + this.updateCheckDao = updateCheckDao; this.context = context; } @@ -71,7 +71,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back return Optional.absent(); } - final UpdateCheckEntity entity = database.load(UpdateCheckEntity.class, 1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); if (entity.getVersion() != null && entity.getVersion().equals(latestVersion.version) && entity.getApkSha256() != null) { return Optional.of(new UpdateCheckImpl("", entity)); @@ -82,7 +82,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back entity.setVersion(updateCheck.getVersion()); entity.setApkSha256(updateCheck.getApkSha256()); - database.store(entity); + updateCheckDao.storeReplacing(entity); return Optional.of(updateCheck); } @@ -90,22 +90,22 @@ public Optional getUpdateCheck(final String appVersion) throws Back @Nullable @Override public String getLicense() { - return database.load(UpdateCheckEntity.class, 1L).getLicenseToken(); + return updateCheckDao.load(1L).getLicenseToken(); } @Override public void setLicense(String license) { - final UpdateCheckEntity entity = database.load(UpdateCheckEntity.class, 1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); entity.setLicenseToken(license); - database.store(entity); + updateCheckDao.storeReplacing(entity); } @Override public void update(File file) throws GeneralUpdateErrorException { try { - final UpdateCheckEntity entity = database.load(UpdateCheckEntity.class, 1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); final Request request = new Request // .Builder() // diff --git a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java index 2c4e17934..ec403e415 100644 --- a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java @@ -1,11 +1,12 @@ package org.cryptomator.data.repository; +import static org.cryptomator.domain.Vault.aCopyOf; + import android.database.sqlite.SQLiteConstraintException; import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory; import org.cryptomator.data.cloud.crypto.CryptoCloudFactory; -import org.cryptomator.data.db.Database; -import org.cryptomator.data.db.entities.VaultEntity; +import org.cryptomator.data.db.VaultDao; import org.cryptomator.data.db.mappers.VaultEntityMapper; import org.cryptomator.domain.Vault; import org.cryptomator.domain.exception.BackendException; @@ -18,12 +19,10 @@ import javax.inject.Inject; import javax.inject.Singleton; -import static org.cryptomator.domain.Vault.aCopyOf; - @Singleton class VaultRepositoryImpl implements VaultRepository { - private final Database database; + private final VaultDao vaultDao; private final VaultEntityMapper mapper; private final CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -35,9 +34,9 @@ public VaultRepositoryImpl( // CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory, // CryptoCloudFactory cryptoCloudFactory, // DispatchingCloudContentRepository dispatchingCloudContentRepository, // - Database database) { + VaultDao vaultDao) { this.mapper = mapper; - this.database = database; + this.vaultDao = vaultDao; this.cryptoCloudContentRepositoryFactory = cryptoCloudContentRepositoryFactory; this.cryptoCloudFactory = cryptoCloudFactory; this.dispatchingCloudContentRepository = dispatchingCloudContentRepository; @@ -46,7 +45,7 @@ public VaultRepositoryImpl( // @Override public List vaults() throws BackendException { List result = new ArrayList<>(); - for (Vault vault : mapper.fromEntities(database.loadAll(VaultEntity.class))) { + for (Vault vault : mapper.fromEntities(vaultDao.loadAll())) { result.add(aCopyOf(vault).withUnlocked(isUnlocked(vault)).build()); } return result; @@ -55,7 +54,7 @@ public List vaults() throws BackendException { @Override public Vault store(Vault vault) throws BackendException { try { - return mapper.fromEntity(database.store(mapper.toEntity(vault))); + return mapper.fromEntity(vaultDao.storeReplacingAndReload(mapper.toEntity(vault))); } catch (SQLiteConstraintException e) { throw new VaultAlreadyExistException(); } @@ -65,13 +64,13 @@ public Vault store(Vault vault) throws BackendException { public Long delete(Vault vault) throws BackendException { deregisterUnlocked(vault); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cryptoCloudFactory.decryptedViewOf(vault)); - database.delete(mapper.toEntity(vault)); + vaultDao.delete(mapper.toEntity(vault)); return vault.getId(); } @Override public Vault load(Long id) throws BackendException { - Vault vault = mapper.fromEntity(database.load(VaultEntity.class, id)); + Vault vault = mapper.fromEntity(vaultDao.load(id)); return aCopyOf(vault).withUnlocked(isUnlocked(vault)).build(); } diff --git a/presentation/build.gradle b/presentation/build.gradle index ec554282b..692a5d8f8 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -151,6 +151,9 @@ dependencies { implementation project(':subsampling-image-view') implementation dependencies.recyclerViewFastScroll + // room (required for adding DatabaseModule to the dagger graph) + implementation dependencies.room + // android x implementation dependencies.androidxCore implementation dependencies.androidxFragment From c6ba13a8ac8b34111e25af2f3cf1e0d8531b85ee Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:12:38 +0200 Subject: [PATCH 002/120] Simplified database schema Introduced database version 14 --- .../14.json | 229 ++++++++++++++++++ .../java/org/cryptomator/data/db/CloudDao.kt | 2 +- .../data/db/CryptomatorDatabase.kt | 8 +- .../org/cryptomator/data/db/UpdateCheckDao.kt | 2 +- .../java/org/cryptomator/data/db/VaultDao.kt | 2 +- .../data/db/entities/CloudEntity.kt | 19 +- .../data/db/entities/UpdateCheckEntity.kt | 15 +- .../data/db/entities/VaultEntity.kt | 23 +- .../data/db/migrations/Migration13To14.kt | 35 +++ 9 files changed, 301 insertions(+), 34 deletions(-) create mode 100644 data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json create mode 100644 data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json new file mode 100644 index 000000000..094c0d991 --- /dev/null +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json @@ -0,0 +1,229 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "a2a147e1f1c849da62d2c334c3c3d49a", + "entities": [ + { + "tableName": "CLOUD_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `type` TEXT NOT NULL, `accessToken` TEXT, `url` TEXT, `username` TEXT, `webdavCertificate` TEXT, `s3Bucket` TEXT, `s3Region` TEXT, `s3SecretKey` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webdavCertificate", + "columnName": "webdavCertificate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Bucket", + "columnName": "s3Bucket", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Region", + "columnName": "s3Region", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3SecretKey", + "columnName": "s3SecretKey", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UPDATE_CHECK_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `licenseToken` TEXT, `releaseNote` TEXT, `version` TEXT, `urlToApk` TEXT, `apkSha256` TEXT, `urlToReleaseNote` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "licenseToken", + "columnName": "licenseToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "releaseNote", + "columnName": "releaseNote", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToApk", + "columnName": "urlToApk", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "apkSha256", + "columnName": "apkSha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToReleaseNote", + "columnName": "urlToReleaseNote", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "VAULT_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `folderCloudId` INTEGER, `folderPath` TEXT, `folderName` TEXT, `cloudType` TEXT NOT NULL, `password` TEXT, `position` INTEGER, `format` INTEGER, `shorteningThreshold` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`folderCloudId`) REFERENCES `CLOUD_ENTITY`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderCloudId", + "columnName": "folderCloudId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderPath", + "columnName": "folderPath", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folderName", + "columnName": "folderName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cloudType", + "columnName": "cloudType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shorteningThreshold", + "columnName": "shorteningThreshold", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", + "unique": true, + "columnNames": [ + "folderPath", + "folderCloudId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`folderPath`, `folderCloudId`)" + } + ], + "foreignKeys": [ + { + "table": "CLOUD_ENTITY", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "folderCloudId" + ], + "referencedColumns": [ + "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, 'a2a147e1f1c849da62d2c334c3c3d49a')" + ] + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt index 4f0f2b5b4..3a942dc3b 100644 --- a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt @@ -12,7 +12,7 @@ import org.cryptomator.data.db.entities.VaultEntity @Dao interface CloudDao { - @Query("SELECT * FROM CLOUD_ENTITY WHERE _id = :id LIMIT 1") + @Query("SELECT * FROM CLOUD_ENTITY WHERE id = :id LIMIT 1") fun load(id: Long): CloudEntity @Query("SELECT * from CLOUD_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 74c3181eb..054c14bb6 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -1,12 +1,18 @@ package org.cryptomator.data.db +import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import org.cryptomator.data.db.entities.CloudEntity import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity +import org.cryptomator.data.db.migrations.Migration13To14 -@Database(version = 13, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class]) +@Database( + version = 14, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class], autoMigrations = [ + AutoMigration(from = 13, to = 14, spec = Migration13To14::class) + ] +) abstract class CryptomatorDatabase : RoomDatabase() { abstract fun cloudDao(): CloudDao diff --git a/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt index 623cf52b7..0d28df3a8 100644 --- a/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt @@ -11,7 +11,7 @@ import org.cryptomator.data.db.entities.UpdateCheckEntity @Dao interface UpdateCheckDao { - @Query("SELECT * FROM UPDATE_CHECK_ENTITY WHERE _id = :id LIMIT 1") + @Query("SELECT * FROM UPDATE_CHECK_ENTITY WHERE id = :id LIMIT 1") fun load(id: Long): UpdateCheckEntity @Query("SELECT * from UPDATE_CHECK_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/VaultDao.kt b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt index 46c93e6a1..229dc8b87 100644 --- a/data/src/main/java/org/cryptomator/data/db/VaultDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt @@ -11,7 +11,7 @@ import org.cryptomator.data.db.entities.VaultEntity @Dao interface VaultDao { - @Query("SELECT * FROM VAULT_ENTITY WHERE _id = :id LIMIT 1") + @Query("SELECT * FROM VAULT_ENTITY WHERE id = :id LIMIT 1") fun load(id: Long): VaultEntity @Query("SELECT * from VAULT_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt index 82dc09c9a..33cad47ce 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt @@ -1,6 +1,5 @@ package org.cryptomator.data.db.entities -import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @@ -8,13 +7,13 @@ import androidx.room.PrimaryKey data class CloudEntity @JvmOverloads constructor( //TODO Remove @JvmOverloads //TODO Nullability - @PrimaryKey @ColumnInfo("_id") override var id: Long?, - @ColumnInfo("TYPE") var type: String, - @ColumnInfo("ACCESS_TOKEN") var accessToken: String? = null, - @ColumnInfo("URL") var url: String? = null, - @ColumnInfo("USERNAME") var username: String? = null, - @ColumnInfo("WEBDAV_CERTIFICATE") var webdavCertificate: String? = null, - @ColumnInfo("S3_BUCKET") var s3Bucket: String? = null, - @ColumnInfo("S3_REGION") var s3Region: String? = null, - @ColumnInfo("S3_SECRET_KEY") var s3SecretKey: String? = null, + @PrimaryKey override var id: Long?, + var type: String, + var accessToken: String? = null, + var url: String? = null, + var username: String? = null, + var webdavCertificate: String? = null, + var s3Bucket: String? = null, + var s3Region: String? = null, + var s3SecretKey: String? = null, ) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt index cd53cef9d..a69920fe3 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt @@ -1,17 +1,16 @@ package org.cryptomator.data.db.entities -import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "UPDATE_CHECK_ENTITY") data class UpdateCheckEntity @JvmOverloads constructor( //TODO Remove @JvmOverloads - @PrimaryKey @ColumnInfo("_id") override var id: Long?, - @ColumnInfo("LICENSE_TOKEN") var licenseToken: String?, - @ColumnInfo("RELEASE_NOTE") var releaseNote: String?, - @ColumnInfo("VERSION") var version: String?, - @ColumnInfo("URL_TO_APK") var urlToApk: String?, - @ColumnInfo("APK_SHA256") var apkSha256: String?, - @ColumnInfo("URL_TO_RELEASE_NOTE") var urlToReleaseNote: String?, + @PrimaryKey override var id: Long?, + var licenseToken: String?, + var releaseNote: String?, + var version: String?, + var urlToApk: String?, + var apkSha256: String?, + var urlToReleaseNote: String?, ) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index e99b4ead6..faf8f7b12 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -1,6 +1,5 @@ package org.cryptomator.data.db.entities -import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index @@ -8,17 +7,17 @@ import androidx.room.PrimaryKey @Entity( tableName = "VAULT_ENTITY", // - indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["FOLDER_PATH", "FOLDER_CLOUD_ID"], unique = true)], // - foreignKeys = [ForeignKey(CloudEntity::class, ["_id"], ["FOLDER_CLOUD_ID"], onDelete = ForeignKey.SET_NULL)] + indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["folderPath", "folderCloudId"], unique = true)], // + foreignKeys = [ForeignKey(CloudEntity::class, ["id"], ["folderCloudId"], onDelete = ForeignKey.SET_NULL)] ) data class VaultEntity constructor( - @PrimaryKey @ColumnInfo("_id") override val id: Long?, - @ColumnInfo("FOLDER_CLOUD_ID") val folderCloudId: Long?, //TODO Map to CloudEntity - @ColumnInfo("FOLDER_PATH") val folderPath: String?, - @ColumnInfo("FOLDER_NAME") val folderName: String?, - @ColumnInfo("CLOUD_TYPE") val cloudType: String, - @ColumnInfo("PASSWORD") val password: String?, - @ColumnInfo("POSITION") val position: Int?, - @ColumnInfo("FORMAT") val format: Int?, - @ColumnInfo("SHORTENING_THRESHOLD") val shorteningThreshold: Int?, + @PrimaryKey override val id: Long?, + val folderCloudId: Long?, //TODO Map to CloudEntity + val folderPath: String?, + val folderName: String?, + val cloudType: String, + val password: String?, + val position: Int?, + val format: Int?, + val shorteningThreshold: Int?, ) : DatabaseEntity \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt b/data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt new file mode 100644 index 000000000..290b6a590 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt @@ -0,0 +1,35 @@ +package org.cryptomator.data.db.migrations + +import androidx.room.RenameColumn +import androidx.room.migration.AutoMigrationSpec + +@RenameColumn.Entries( + RenameColumn("CLOUD_ENTITY", "_id", "id"), + RenameColumn("CLOUD_ENTITY", "TYPE", "type"), + RenameColumn("CLOUD_ENTITY", "ACCESS_TOKEN", "accessToken"), + RenameColumn("CLOUD_ENTITY", "URL", "url"), + RenameColumn("CLOUD_ENTITY", "USERNAME", "username"), + RenameColumn("CLOUD_ENTITY", "WEBDAV_CERTIFICATE", "webdavCertificate"), + RenameColumn("CLOUD_ENTITY", "S3_BUCKET", "s3Bucket"), + RenameColumn("CLOUD_ENTITY", "S3_REGION", "s3Region"), + RenameColumn("CLOUD_ENTITY", "S3_SECRET_KEY", "s3SecretKey"), + // + RenameColumn("UPDATE_CHECK_ENTITY", "_id", "id"), + RenameColumn("UPDATE_CHECK_ENTITY", "LICENSE_TOKEN", "licenseToken"), + RenameColumn("UPDATE_CHECK_ENTITY", "RELEASE_NOTE", "releaseNote"), + RenameColumn("UPDATE_CHECK_ENTITY", "VERSION", "version"), + RenameColumn("UPDATE_CHECK_ENTITY", "URL_TO_APK", "urlToApk"), + RenameColumn("UPDATE_CHECK_ENTITY", "APK_SHA256", "apkSha256"), + RenameColumn("UPDATE_CHECK_ENTITY", "URL_TO_RELEASE_NOTE", "urlToReleaseNote"), + // + RenameColumn("VAULT_ENTITY", "_id", "id"), + RenameColumn("VAULT_ENTITY", "FOLDER_CLOUD_ID", "folderCloudId"), + RenameColumn("VAULT_ENTITY", "FOLDER_PATH", "folderPath"), + RenameColumn("VAULT_ENTITY", "FOLDER_NAME", "folderName"), + RenameColumn("VAULT_ENTITY", "CLOUD_TYPE", "cloudType"), + RenameColumn("VAULT_ENTITY", "PASSWORD", "password"), + RenameColumn("VAULT_ENTITY", "POSITION", "position"), + RenameColumn("VAULT_ENTITY", "FORMAT", "format"), + RenameColumn("VAULT_ENTITY", "SHORTENING_THRESHOLD", "shorteningThreshold"), +) +class Migration13To14 : AutoMigrationSpec \ No newline at end of file From 1e60faa8885f2ea438584c2d56be19323e9b2be0 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:18:53 +0200 Subject: [PATCH 003/120] Removed unnecessary historic method "clearCache" The method was a delegate to the "DaoSession#clear" method provided by greenDAO. Analysis of the generated code indicates that room doesn't cache results internally. Therefore clearing the cache to evict stale entries is unnecessary. See: https://web.archive.org/web/20230806043911/https://greenrobot.org/greendao/documentation/sessions/ --- .../main/java/org/cryptomator/data/db/CryptomatorDatabase.kt | 4 ---- .../org/cryptomator/data/repository/CloudRepositoryImpl.java | 1 - 2 files changed, 5 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 054c14bb6..9cbf6669f 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -20,8 +20,4 @@ abstract class CryptomatorDatabase : RoomDatabase() { abstract fun updateCheckDao(): UpdateCheckDao abstract fun vaultDao(): VaultDao - - fun clearCache() { - //TODO - } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index 1f6b9e0ae..a9582cc8f 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -66,7 +66,6 @@ public Cloud store(Cloud cloud) { } Cloud storedCloud = mapper.fromEntity(database.cloudDao().storeReplacingAndReload(mapper.toEntity(cloud))); - database.clearCache(); dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud); From 10fd4688e7263137fcb390242e9703c3bf5b3478 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 19 Oct 2023 20:43:03 +0200 Subject: [PATCH 004/120] Added logging/comments to database configuration --- .../org/cryptomator/data/db/DatabaseModule.kt | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 8e5e796e4..9c9e064df 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -2,11 +2,14 @@ package org.cryptomator.data.db import android.content.Context import androidx.room.Room +import androidx.room.RoomDatabase import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.migrations.Migration12To13 import javax.inject.Singleton import dagger.Module import dagger.Provides +import timber.log.Timber @Module class DatabaseModule { @@ -14,10 +17,15 @@ class DatabaseModule { @Singleton @Provides fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { + Timber.tag("Database").i("Building database") return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // .addMigrations(*migrations) // .addMigrations(Migration12To13) // - .build() + .addCallback(DatabaseCallback) // + .build() //Fails if no migration is found (especially when downgrading) + .also { // + Timber.tag("Database").i("Database built successfully") + } } @Singleton @@ -67,4 +75,20 @@ class DatabaseModule { upgrade10To11, upgrade11To12, ) +} + +object DatabaseCallback : RoomDatabase.Callback() { + + override fun onCreate(db: SupportSQLiteDatabase) { + Timber.tag("Database").i("Created database (v%s)", db.version) + } + + override fun onOpen(db: SupportSQLiteDatabase) { + Timber.tag("Database").i("Opened database (v%s)", db.version) + } + + override fun onDestructiveMigration(db: SupportSQLiteDatabase) { + //This should not be called + throw UnsupportedOperationException("Destructive migration is not supported") + } } \ No newline at end of file From 068818e4d421291c544dbc100d0cf2bf69179b54 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:24:35 +0200 Subject: [PATCH 005/120] Added logging to legacy/manual migrations --- .../cryptomator/data/db/DatabaseMigration.kt | 17 +++++++++++++++++ .../java/org/cryptomator/data/db/Upgrade0To1.kt | 5 ++--- .../org/cryptomator/data/db/Upgrade10To11.kt | 5 ++--- .../org/cryptomator/data/db/Upgrade11To12.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade1To2.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade2To3.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade3To4.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade4To5.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade5To6.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade6To7.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade7To8.kt | 5 ++--- .../java/org/cryptomator/data/db/Upgrade8To9.kt | 5 ++--- .../org/cryptomator/data/db/Upgrade9To10.kt | 5 ++--- .../data/db/migrations/Migration12To13.kt | 6 +++--- 14 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt new file mode 100644 index 000000000..182f221a6 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt @@ -0,0 +1,17 @@ +package org.cryptomator.data.db + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import timber.log.Timber + + +abstract class DatabaseMigration(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) { + + final override fun migrate(database: SupportSQLiteDatabase) { + Timber.tag("DatabaseMigration").i("Running %s (%d -> %d)", javaClass.simpleName, startVersion, endVersion) + migrateInternal(database) + } + + protected abstract fun migrateInternal(db: SupportSQLiteDatabase) + +} diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt index bbb1ecb21..c2a73ad7a 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt @@ -1,6 +1,5 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour import org.cryptomator.domain.CloudType @@ -8,9 +7,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade0To1 @Inject constructor() : Migration(0, 1) { +internal class Upgrade0To1 @Inject constructor() : DatabaseMigration(0, 1) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { createCloudEntityTable(db) createVaultEntityTable(db) createDropboxCloud(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt index 3b5d06e5d..5f4732121 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt @@ -1,18 +1,17 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade10To11 @Inject constructor() : Migration(10, 11) { +internal class Upgrade10To11 @Inject constructor() : DatabaseMigration(10, 11) { private val defaultThreshold = 220 private val defaultVaultFormat = 8 private val onedriveCloudId = 3L - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { addFormatAndShorteningToDbEntity(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt index 181b4688d..ccbc7ec9a 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt @@ -1,6 +1,5 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.google.common.base.Optional import org.cryptomator.util.SharedPreferencesHandler @@ -8,9 +7,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade11To12 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(11, 12) { +internal class Upgrade11To12 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseMigration(11, 12) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { when (sharedPreferencesHandler.updateIntervalInDays()) { Optional.of(7), Optional.of(30) -> sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(1)) } diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt index ca3f0552b..4a698b43a 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt @@ -1,14 +1,13 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade1To2 @Inject constructor() : Migration(1, 2) { +internal class Upgrade1To2 @Inject constructor() : DatabaseMigration(1, 2) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { createUpdateCheckTable(db) createInitialUpdateStatus(db) } diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt index dfe9ef86e..0abdab36d 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt @@ -2,16 +2,15 @@ package org.cryptomator.data.db import android.content.Context import android.content.SharedPreferences -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.util.crypto.CredentialCryptor import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade2To3 @Inject constructor(private val context: Context) : Migration(2, 3) { +internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseMigration(2, 3) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { Sql.query("CLOUD_ENTITY") diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt index f801beb61..6468cea0a 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt @@ -1,15 +1,14 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade3To4 @Inject constructor() : Migration(3, 4) { +internal class Upgrade3To4 @Inject constructor() : DatabaseMigration(3, 4) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { addPositionToVaultSchema(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt index 6e853998e..606a0cb05 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt @@ -1,14 +1,13 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade4To5 @Inject constructor() : Migration(4, 5) { +internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeWebdavUrlInCloudEntityToUrl(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt index de48c8d41..c17395471 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt @@ -1,14 +1,13 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade5To6 @Inject constructor() : Migration(5, 6) { +internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeCloudEntityToSupportS3(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt index 9f761255b..b7b5fca75 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt @@ -1,16 +1,15 @@ package org.cryptomator.data.db import android.database.sqlite.SQLiteException -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton import timber.log.Timber @Singleton -internal class Upgrade6To7 @Inject constructor() : Migration(6, 7) { +internal class Upgrade6To7 @Inject constructor() : DatabaseMigration(6, 7) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { changeUpdateEntityToSupportSha256Verification(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt index 986614f37..a5a784353 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt @@ -1,14 +1,13 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade7To8 @Inject constructor() : Migration(7, 8) { +internal class Upgrade7To8 @Inject constructor() : DatabaseMigration(7, 8) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { dropS3Vaults(db) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt index 82d9a9af1..632fe1d19 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt @@ -1,15 +1,14 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.util.SharedPreferencesHandler import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade8To9 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(8, 9) { +internal class Upgrade8To9 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseMigration(8, 9) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { // toggle beta screen dialog already shown to display it again in this beta sharedPreferencesHandler.setBetaScreenDialogAlreadyShown(false) } diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt index 44de8f62b..c897934a6 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt @@ -1,6 +1,5 @@ package org.cryptomator.data.db -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler @@ -9,11 +8,11 @@ import javax.inject.Singleton import timber.log.Timber @Singleton -internal class Upgrade9To10 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : Migration(9, 10) { +internal class Upgrade9To10 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseMigration(9, 10) { private val defaultLocalStorageCloudId = 4L - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt index 5ebdb237b..6c4347784 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt @@ -1,12 +1,12 @@ package org.cryptomator.data.db.migrations -import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration //TODO Verify if Metadata from earlier tables was lost -object Migration12To13 : Migration(12, 13) { +object Migration12To13 : DatabaseMigration(12, 13) { - override fun migrate(database: SupportSQLiteDatabase) { + override fun migrateInternal(db: SupportSQLiteDatabase) { //NO-OP } } \ No newline at end of file From cde0c78efd6968fc48276576dff739079da8959c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:09:28 +0200 Subject: [PATCH 006/120] Reorganized migrations --- .../org/cryptomator/data/db/CryptomatorDatabase.kt | 4 ++-- .../java/org/cryptomator/data/db/DatabaseModule.kt | 14 +++++++++++++- .../cryptomator/data/db/{ => migrations}/Sql.java | 14 +++++++------- .../AutoMigration13To14.kt} | 4 ++-- .../data/db/{ => migrations/legacy}/Upgrade0To1.kt | 6 ++++-- .../db/{ => migrations/legacy}/Upgrade10To11.kt | 4 +++- .../db/{ => migrations/legacy}/Upgrade11To12.kt | 3 ++- .../data/db/{ => migrations/legacy}/Upgrade1To2.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade2To3.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade3To4.kt | 6 ++++-- .../data/db/{ => migrations/legacy}/Upgrade4To5.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade5To6.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade6To7.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade7To8.kt | 4 +++- .../data/db/{ => migrations/legacy}/Upgrade8To9.kt | 3 ++- .../db/{ => migrations/legacy}/Upgrade9To10.kt | 4 +++- .../db/migrations/{ => manual}/Migration12To13.kt | 2 +- 17 files changed, 61 insertions(+), 27 deletions(-) rename data/src/main/java/org/cryptomator/data/db/{ => migrations}/Sql.java (96%) rename data/src/main/java/org/cryptomator/data/db/migrations/{Migration13To14.kt => auto/AutoMigration13To14.kt} (94%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade0To1.kt (91%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade10To11.kt (94%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade11To12.kt (85%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade1To2.kt (88%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade2To3.kt (91%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade3To4.kt (89%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade4To5.kt (94%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade5To6.kt (94%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade6To7.kt (94%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade7To8.kt (82%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade8To9.kt (83%) rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade9To10.kt (91%) rename data/src/main/java/org/cryptomator/data/db/migrations/{ => manual}/Migration12To13.kt (84%) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 9cbf6669f..a851cc327 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -6,11 +6,11 @@ import androidx.room.RoomDatabase import org.cryptomator.data.db.entities.CloudEntity import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity -import org.cryptomator.data.db.migrations.Migration13To14 +import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 @Database( version = 14, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class], autoMigrations = [ - AutoMigration(from = 13, to = 14, spec = Migration13To14::class) + AutoMigration(from = 13, to = 14, spec = AutoMigration13To14::class) ] ) abstract class CryptomatorDatabase : RoomDatabase() { diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 9c9e064df..39ebb845a 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -5,7 +5,19 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import org.cryptomator.data.db.migrations.Migration12To13 +import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 +import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 +import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 +import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 +import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 +import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 +import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 +import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 +import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 +import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 +import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 +import org.cryptomator.data.db.migrations.manual.Migration12To13 import javax.inject.Singleton import dagger.Module import dagger.Provides diff --git a/data/src/main/java/org/cryptomator/data/db/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java similarity index 96% rename from data/src/main/java/org/cryptomator/data/db/Sql.java rename to data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 2b510ec7a..8fc15bd87 100644 --- a/data/src/main/java/org/cryptomator/data/db/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -1,10 +1,10 @@ -package org.cryptomator.data.db; +package org.cryptomator.data.db.migrations; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; -import static org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ColumnType.TEXT; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.TEXT; import static java.lang.String.format; import android.content.ContentValues; @@ -19,7 +19,7 @@ //TODO Use precompiled statements for all? //TODO Compatibility of Rename table with new androids //https://developer.android.com/reference/android/database/sqlite/package-summary.html -- API 26 -> 3.18 -class Sql { +public class Sql { public static SqlInsertBuilder insertInto(String table) { return new SqlInsertBuilder(table); diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt index 290b6a590..364b22ad0 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Migration13To14.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.db.migrations +package org.cryptomator.data.db.migrations.auto import androidx.room.RenameColumn import androidx.room.migration.AutoMigrationSpec @@ -32,4 +32,4 @@ import androidx.room.migration.AutoMigrationSpec RenameColumn("VAULT_ENTITY", "FORMAT", "format"), RenameColumn("VAULT_ENTITY", "SHORTENING_THRESHOLD", "shorteningThreshold"), ) -class Migration13To14 : AutoMigrationSpec \ No newline at end of file +class AutoMigration13To14 : AutoMigrationSpec \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt similarity index 91% rename from data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt index c2a73ad7a..bd61f883d 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade0To1.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt @@ -1,7 +1,9 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase -import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql +import org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour import org.cryptomator.domain.CloudType import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt index 5f4732121..29acffd05 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade10To11.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade11To12.kt similarity index 85% rename from data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade11To12.kt index ccbc7ec9a..7b81d69a9 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade11To12.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade11To12.kt @@ -1,7 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase import com.google.common.base.Optional +import org.cryptomator.data.db.DatabaseMigration import org.cryptomator.util.SharedPreferencesHandler import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt similarity index 88% rename from data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt index 4a698b43a..b4eb5a2bf 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade1To2.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade2To3.kt similarity index 91% rename from data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade2To3.kt index 0abdab36d..566410960 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade2To3.kt @@ -1,8 +1,10 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import android.content.Context import android.content.SharedPreferences import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import org.cryptomator.util.crypto.CredentialCryptor import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt similarity index 89% rename from data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt index 6468cea0a..9dd3d0cc2 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt @@ -1,7 +1,9 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase -import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql +import org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt index 606a0cb05..acb509c57 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt index c17395471..c379b8f36 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt index b7b5fca75..a89e5e6b2 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade6To7.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt @@ -1,7 +1,9 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import android.database.sqlite.SQLiteException import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton import timber.log.Timber diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade7To8.kt similarity index 82% rename from data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade7To8.kt index a5a784353..f6f3a145e 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade7To8.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade7To8.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade8To9.kt similarity index 83% rename from data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade8To9.kt index 632fe1d19..fd9ce59fd 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade8To9.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade8To9.kt @@ -1,6 +1,7 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration import org.cryptomator.util.SharedPreferencesHandler import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade9To10.kt similarity index 91% rename from data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade9To10.kt index c897934a6..50c964edd 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade9To10.kt @@ -1,6 +1,8 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler import javax.inject.Inject diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt similarity index 84% rename from data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt index 6c4347784..a9b99059c 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Migration12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.db.migrations +package org.cryptomator.data.db.migrations.manual import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.DatabaseMigration From b2aeef93657ec0172f61306713b42f0010feafd3 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:44:58 +0200 Subject: [PATCH 007/120] Refactored injection of Migration12To13 --- .../src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 5 ++++- .../data/db/migrations/manual/Migration12To13.kt | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 39ebb845a..0bef7a3a3 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -32,7 +32,6 @@ class DatabaseModule { Timber.tag("Database").i("Building database") return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // .addMigrations(*migrations) // - .addMigrations(Migration12To13) // .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) .also { // @@ -73,6 +72,8 @@ class DatabaseModule { upgrade9To10: Upgrade9To10, // upgrade10To11: Upgrade10To11, // upgrade11To12: Upgrade11To12, // + // + migration12To13: Migration12To13, // ): Array = arrayOf( upgrade0To1, upgrade1To2, @@ -86,6 +87,8 @@ class DatabaseModule { upgrade9To10, upgrade10To11, upgrade11To12, + // + migration12To13, ) } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt index a9b99059c..a2b1fb9e3 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt @@ -2,9 +2,11 @@ package org.cryptomator.data.db.migrations.manual import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.DatabaseMigration +import javax.inject.Inject +import javax.inject.Singleton -//TODO Verify if Metadata from earlier tables was lost -object Migration12To13 : DatabaseMigration(12, 13) { +@Singleton +internal class Migration12To13 @Inject constructor() : DatabaseMigration(12, 13) { override fun migrateInternal(db: SupportSQLiteDatabase) { //NO-OP From 4809e4a8ff642a3a97ae89a14966840ad3d9c2b3 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:45:12 +0200 Subject: [PATCH 008/120] Added logging to auto migrations --- .../data/db/DatabaseAutoMigrationSpec.kt | 17 +++++++++++++++++ .../db/migrations/auto/AutoMigration13To14.kt | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt new file mode 100644 index 000000000..4d6d46a14 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt @@ -0,0 +1,17 @@ +package org.cryptomator.data.db + +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase +import timber.log.Timber + + +abstract class DatabaseAutoMigrationSpec : AutoMigrationSpec { + + final override fun onPostMigrate(db: SupportSQLiteDatabase) { + Timber.tag("DatabaseMigration").i("Ran automatic migration %s", javaClass.simpleName) + onPostMigrateInternal(db) + } + + protected open fun onPostMigrateInternal(db: SupportSQLiteDatabase) {} + +} diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt index 364b22ad0..bb2f72848 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt @@ -2,6 +2,8 @@ package org.cryptomator.data.db.migrations.auto import androidx.room.RenameColumn import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseAutoMigrationSpec @RenameColumn.Entries( RenameColumn("CLOUD_ENTITY", "_id", "id"), @@ -32,4 +34,4 @@ import androidx.room.migration.AutoMigrationSpec RenameColumn("VAULT_ENTITY", "FORMAT", "format"), RenameColumn("VAULT_ENTITY", "SHORTENING_THRESHOLD", "shorteningThreshold"), ) -class AutoMigration13To14 : AutoMigrationSpec \ No newline at end of file +class AutoMigration13To14 : DatabaseAutoMigrationSpec() \ No newline at end of file From f1421b3e011eaba3f6f9fe7ba71958c99e391c30 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:01:56 +0200 Subject: [PATCH 009/120] Reformatted "org.cryptomator.data.db" package [ci skip] --- .../java/org/cryptomator/data/db/CloudDao.kt | 1 - .../data/db/mappers/VaultEntityMapper.java | 4 ++-- .../cryptomator/data/db/migrations/Sql.java | 19 ++++++++++--------- .../db/migrations/auto/AutoMigration13To14.kt | 2 -- .../data/db/migrations/legacy/Upgrade4To5.kt | 2 +- .../data/db/migrations/legacy/Upgrade5To6.kt | 2 +- .../data/repository/VaultRepositoryImpl.java | 4 ++-- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt index 3a942dc3b..905a39eb6 100644 --- a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt @@ -7,7 +7,6 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import org.cryptomator.data.db.entities.CloudEntity -import org.cryptomator.data.db.entities.VaultEntity @Dao interface CloudDao { diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java index 6bc91cd90..59707ba69 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java @@ -1,7 +1,5 @@ package org.cryptomator.data.db.mappers; -import static org.cryptomator.domain.Vault.aVault; - import org.cryptomator.data.db.CloudDao; import org.cryptomator.data.db.entities.VaultEntity; import org.cryptomator.domain.Cloud; @@ -12,6 +10,8 @@ import javax.inject.Inject; import javax.inject.Singleton; +import static org.cryptomator.domain.Vault.aVault; + @Singleton public class VaultEntityMapper extends EntityMapper { diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 8fc15bd87..c34606fda 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -1,12 +1,5 @@ package org.cryptomator.data.db.migrations; -import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; -import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; -import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; -import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; -import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.TEXT; -import static java.lang.String.format; - import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; @@ -16,6 +9,13 @@ import java.util.ArrayList; import java.util.List; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.NOT_NULL; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnConstraint.PRIMARY_KEY; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.BOOLEAN; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.INTEGER; +import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.TEXT; +import static java.lang.String.format; + //TODO Use precompiled statements for all? //TODO Compatibility of Rename table with new androids //https://developer.android.com/reference/android/database/sqlite/package-summary.html -- API 26 -> 3.18 @@ -125,10 +125,10 @@ public SqlQueryBuilder where(String column, Criterion criterion) { } public Cursor executeOn(SupportSQLiteDatabase db) { - if(columns == null || columns.isEmpty()) { + if (columns == null || columns.isEmpty()) { throw new IllegalArgumentException(); } - if(tableName == null || tableName.trim().isEmpty()) { + if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException(); } @@ -489,6 +489,7 @@ public void executeOn(SupportSQLiteDatabase db) { } private static final int NOT_FOUND = -1; + private static void appendColumns(StringBuilder query, String[] columns, String sourceTableName, boolean appendSourceTableName) { boolean notFirst = false; diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt index bb2f72848..e1778cec5 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt @@ -1,8 +1,6 @@ package org.cryptomator.data.db.migrations.auto import androidx.room.RenameColumn -import androidx.room.migration.AutoMigrationSpec -import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.DatabaseAutoMigrationSpec @RenameColumn.Entries( diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt index acb509c57..e957f5f16 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt @@ -29,7 +29,7 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { .optionalText("URL") // .optionalText("USERNAME") // .optionalText("WEBDAV_CERTIFICATE") // - .executeOn(db); + .executeOn(db) Sql.insertInto("CLOUD_ENTITY") // .select("_id", "TYPE", "ACCESS_TOKEN", "WEBDAV_URL", "USERNAME", "WEBDAV_CERTIFICATE") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt index c379b8f36..b038da90a 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt @@ -32,7 +32,7 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { .optionalText("S3_BUCKET") // .optionalText("S3_REGION") // .optionalText("S3_SECRET_KEY") // - .executeOn(db); + .executeOn(db) Sql.insertInto("CLOUD_ENTITY") // .select("_id", "TYPE", "ACCESS_TOKEN", "URL", "USERNAME", "WEBDAV_CERTIFICATE") // diff --git a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java index ec403e415..bc6d23a45 100644 --- a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java @@ -1,7 +1,5 @@ package org.cryptomator.data.repository; -import static org.cryptomator.domain.Vault.aCopyOf; - import android.database.sqlite.SQLiteConstraintException; import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory; @@ -19,6 +17,8 @@ import javax.inject.Inject; import javax.inject.Singleton; +import static org.cryptomator.domain.Vault.aCopyOf; + @Singleton class VaultRepositoryImpl implements VaultRepository { From 741212b2e4e611043ccef85a5330efa9945f7f14 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 4 Nov 2023 21:58:22 +0100 Subject: [PATCH 010/120] Fixed formatting introduced by merge commit 4cce1eb3 --- buildsystem/dependencies.gradle | 124 ++++++++++++++++---------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 4757a7791..114121738 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -115,70 +115,70 @@ ext { jsonWebTokenApiVersion = '0.11.5' dependencies = [ - android : "com.google.android:android:${androidVersion}", - androidAnnotations : "androidx.annotation:annotation:${androidSupportAnnotationsVersion}", - appcompat : "androidx.appcompat:appcompat:${androidSupportAppcompatVersion}", - androidxBiometric : "androidx.biometric:biometric:${androidxBiometricVersion}", - androidxCore : "androidx.core:core-ktx:${androidxCoreVersion}", - androidxFragment : "androidx.fragment:fragment-ktx:${androidxFragmentVersion}", - androidxViewpager : "androidx.viewpager:viewpager:${androidxViewpagerVersion}", - androidxSwiperefresh : "androidx.swiperefreshlayout:swiperefreshlayout:${androidxSwiperefreshVersion}", - androidxPreference : "androidx.preference:preference:${androidxPreferenceVersion}", - documentFile : "androidx.documentfile:documentfile:${androidxDocumentfileVersion}", - recyclerView : "androidx.recyclerview:recyclerview:${androidxRecyclerViewVersion}", - androidxSplashscreen : "androidx.core:core-splashscreen:${androidxSplashscreenVersion}", - androidxTestCore : "androidx.test:core:${androidxTestCoreVersion}", - androidxTestJunitKtln : "androidx.test.ext:junit-ktx:${androidxTestJunitKtlnVersion}", - commonsCodec : "commons-codec:commons-codec:${commonsCodecVersion}", - cryptolib : "org.cryptomator:cryptolib:${cryptolibVersion}", - dagger : "com.google.dagger:dagger:${daggerVersion}", - daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}", - design : "com.google.android.material:material:${androidMaterialDesignVersion}", - coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}", - dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}", - espresso : "androidx.test.espresso:espresso-core:${espressoVersion}", - googleApiClientAndroid: "com.google.api-client:google-api-client-android:${googleClientVersion}", - googleApiServicesDrive: "com.google.apis:google-api-services-drive:${googleApiServicesVersion}", - googlePlayServicesAuth: "com.google.android.gms:play-services-auth:${googlePlayServicesVersion}", + android : "com.google.android:android:${androidVersion}", + androidAnnotations : "androidx.annotation:annotation:${androidSupportAnnotationsVersion}", + appcompat : "androidx.appcompat:appcompat:${androidSupportAppcompatVersion}", + androidxBiometric : "androidx.biometric:biometric:${androidxBiometricVersion}", + androidxCore : "androidx.core:core-ktx:${androidxCoreVersion}", + androidxFragment : "androidx.fragment:fragment-ktx:${androidxFragmentVersion}", + androidxViewpager : "androidx.viewpager:viewpager:${androidxViewpagerVersion}", + androidxSwiperefresh : "androidx.swiperefreshlayout:swiperefreshlayout:${androidxSwiperefreshVersion}", + androidxPreference : "androidx.preference:preference:${androidxPreferenceVersion}", + documentFile : "androidx.documentfile:documentfile:${androidxDocumentfileVersion}", + recyclerView : "androidx.recyclerview:recyclerview:${androidxRecyclerViewVersion}", + androidxSplashscreen : "androidx.core:core-splashscreen:${androidxSplashscreenVersion}", + androidxTestCore : "androidx.test:core:${androidxTestCoreVersion}", + androidxTestJunitKtln : "androidx.test.ext:junit-ktx:${androidxTestJunitKtlnVersion}", + commonsCodec : "commons-codec:commons-codec:${commonsCodecVersion}", + cryptolib : "org.cryptomator:cryptolib:${cryptolibVersion}", + dagger : "com.google.dagger:dagger:${daggerVersion}", + daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}", + design : "com.google.android.material:material:${androidMaterialDesignVersion}", + coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}", + dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}", + espresso : "androidx.test.espresso:espresso-core:${espressoVersion}", + googleApiClientAndroid : "com.google.api-client:google-api-client-android:${googleClientVersion}", + googleApiServicesDrive : "com.google.apis:google-api-services-drive:${googleApiServicesVersion}", + googlePlayServicesAuth : "com.google.android.gms:play-services-auth:${googlePlayServicesVersion}", trackingFreeGoogleCLient : files("lib/google-http-client-${trackingFreeGoogleCLientVersion}.jar"), trackingFreeGoogleAndroidCLient: files("lib/google-http-client-android-${trackingFreeGoogleCLientVersion}.jar"), - room : "androidx.room:room-runtime:${roomVersion}", - roomCompiler : "androidx.room:room-compiler:${roomVersion}", - gson : "com.google.code.gson:gson:${gsonVersion}", - hamcrest : "org.hamcrest:hamcrest-all:${hamcrestVersion}", - javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}", - junit : "org.junit.jupiter:junit-jupiter:${jUnitVersion}", - junitApi : "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}", - junitEngine : "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}", - junitParams : "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}", - junit4Engine : "org.junit.vintage:junit-vintage-engine:${jUnitVersion}", - minIo : "io.minio:minio:${minIoVersion}", - mockito : "org.mockito:mockito-core:${mockitoVersion}", - mockitoInline : "org.mockito:mockito-inline:${mockitoVersion}", - mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:${mockitoKotlinVersion}", - msgraph : "com.microsoft.graph:microsoft-graph:${msgraphVersion}", - msgraphAuth : "com.microsoft.identity.client:msal:${msgraphAuthVersion}", - multidex : "androidx.multidex:multidex:${multidexVersion}", - okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}", - okHttpDigest : "io.github.rburgst:okhttp-digest:${okHttpDigestVersion}", - recyclerViewFastScroll: "com.simplecityapps:recyclerview-fastscroll:${recyclerViewFastScrollVersion}", - rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", - rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", - rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxBindingVersion}", - stax : "stax:stax:${staxVersion}", - testingSupportLib : "com.android.support.test:testing-support-lib:${testingSupportLibVersion}", - timber : "com.jakewharton.timber:timber:${timberVersion}", - velocity : "org.apache.velocity:velocity-engine-core:${velocityVersion}", - runner : "androidx.test:runner:${runnerVersion}", - rules : "androidx.test:rules:${rulesVersion}", - contribution : "androidx.test.espresso:espresso-contrib:${contributionVersion}", - uiAutomator : "androidx.test.uiautomator:uiautomator:${uiautomatorVersion}", - zxcvbn : "com.nulab-inc:zxcvbn:${zxcvbnVersion}", - scaleImageView : "com.davemorrissey.labs:subsampling-scale-image-view:${scaleImageViewVersion}", - lruFileCache : "com.github.solkin:disk-lru-cache:${lruFileCacheVersion}", - jsonWebTokenApi : "io.jsonwebtoken:jjwt-api:${jsonWebTokenApiVersion}", - jsonWebTokenImpl : "io.jsonwebtoken:jjwt-impl:${jsonWebTokenApiVersion}", - jsonWebTokenJson : "io.jsonwebtoken:jjwt-orgjson:${jsonWebTokenApiVersion}" + room : "androidx.room:room-runtime:${roomVersion}", + roomCompiler : "androidx.room:room-compiler:${roomVersion}", + gson : "com.google.code.gson:gson:${gsonVersion}", + hamcrest : "org.hamcrest:hamcrest-all:${hamcrestVersion}", + javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}", + junit : "org.junit.jupiter:junit-jupiter:${jUnitVersion}", + junitApi : "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}", + junitEngine : "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}", + junitParams : "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}", + junit4Engine : "org.junit.vintage:junit-vintage-engine:${jUnitVersion}", + minIo : "io.minio:minio:${minIoVersion}", + mockito : "org.mockito:mockito-core:${mockitoVersion}", + mockitoInline : "org.mockito:mockito-inline:${mockitoVersion}", + mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:${mockitoKotlinVersion}", + msgraph : "com.microsoft.graph:microsoft-graph:${msgraphVersion}", + msgraphAuth : "com.microsoft.identity.client:msal:${msgraphAuthVersion}", + multidex : "androidx.multidex:multidex:${multidexVersion}", + okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}", + okHttpDigest : "io.github.rburgst:okhttp-digest:${okHttpDigestVersion}", + recyclerViewFastScroll : "com.simplecityapps:recyclerview-fastscroll:${recyclerViewFastScrollVersion}", + rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", + rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", + rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxBindingVersion}", + stax : "stax:stax:${staxVersion}", + testingSupportLib : "com.android.support.test:testing-support-lib:${testingSupportLibVersion}", + timber : "com.jakewharton.timber:timber:${timberVersion}", + velocity : "org.apache.velocity:velocity-engine-core:${velocityVersion}", + runner : "androidx.test:runner:${runnerVersion}", + rules : "androidx.test:rules:${rulesVersion}", + contribution : "androidx.test.espresso:espresso-contrib:${contributionVersion}", + uiAutomator : "androidx.test.uiautomator:uiautomator:${uiautomatorVersion}", + zxcvbn : "com.nulab-inc:zxcvbn:${zxcvbnVersion}", + scaleImageView : "com.davemorrissey.labs:subsampling-scale-image-view:${scaleImageViewVersion}", + lruFileCache : "com.github.solkin:disk-lru-cache:${lruFileCacheVersion}", + jsonWebTokenApi : "io.jsonwebtoken:jjwt-api:${jsonWebTokenApiVersion}", + jsonWebTokenImpl : "io.jsonwebtoken:jjwt-impl:${jsonWebTokenApiVersion}", + jsonWebTokenJson : "io.jsonwebtoken:jjwt-orgjson:${jsonWebTokenApiVersion}" ] } From ab927868a0a19cf5be3de63c997be918c0d99ab3 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:25:37 +0100 Subject: [PATCH 011/120] Externalized database version for use in other locations --- .../main/java/org/cryptomator/data/db/CryptomatorDatabase.kt | 4 +++- data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index a851cc327..ad38ec5f5 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -8,8 +8,10 @@ import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 +const val CRYPTOMATOR_DATABASE_VERSION = 14 + @Database( - version = 14, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class], autoMigrations = [ + version = CRYPTOMATOR_DATABASE_VERSION, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class], autoMigrations = [ AutoMigration(from = 13, to = 14, spec = AutoMigration13To14::class) ] ) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 0bef7a3a3..5790a0806 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -29,7 +29,7 @@ class DatabaseModule { @Singleton @Provides fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { - Timber.tag("Database").i("Building database") + Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // From 712194f4e428b76dc565c30a616f2f37ed9af4ba Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 4 Nov 2023 21:08:14 +0100 Subject: [PATCH 012/120] Started fixing UpgradeDatabaseTest --- .../data/db/UpgradeDatabaseTest.kt | 296 +++++++++--------- 1 file changed, 154 insertions(+), 142 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 409ed2b42..99b63853d 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -1,21 +1,29 @@ package org.cryptomator.data.db -/* import android.content.Context -import android.database.sqlite.SQLiteDatabase +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.google.common.base.Optional -import org.cryptomator.data.db.entities.CloudEntityDao -import org.cryptomator.data.db.entities.UpdateCheckEntityDao -import org.cryptomator.data.db.entities.VaultEntityDao +import org.cryptomator.data.db.migrations.Sql +import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 +import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 +import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 +import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 +import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 +import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 +import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 +import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 +import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 +import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 +import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CredentialCryptor -import org.greenrobot.greendao.database.Database -import org.greenrobot.greendao.database.StandardDatabase -import org.greenrobot.greendao.internal.DaoConfig import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Assert @@ -29,11 +37,16 @@ class UpgradeDatabaseTest { private val context = InstrumentationRegistry.getInstrumentation().context private val sharedPreferencesHandler = SharedPreferencesHandler(context) - private lateinit var db: Database + private lateinit var db: SupportSQLiteDatabase @Before fun setup() { - db = StandardDatabase(SQLiteDatabase.create(null)) + db = SupportSQLiteOpenHelper.Configuration(context, null, object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) {} + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {} + + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } } @After @@ -43,28 +56,28 @@ class UpgradeDatabaseTest { @Test fun upgradeAll() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - - CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll() + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + + /*CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll() VaultEntityDao(DaoConfig(db, VaultEntityDao::class.java)).loadAll() - UpdateCheckEntityDao(DaoConfig(db, UpdateCheckEntityDao::class.java)).loadAll() + UpdateCheckEntityDao(DaoConfig(db, UpdateCheckEntityDao::class.java)).loadAll()*/ //TODO } @Test fun upgrade2To3() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) val url = "url" val username = "username" @@ -89,7 +102,7 @@ class UpgradeDatabaseTest { context.getSharedPreferences("com.microsoft.live", Context.MODE_PRIVATE).edit().putString("refresh_token", accessToken).commit() - Upgrade2To3(context).applyTo(db, 2) + Upgrade2To3(context).migrate(db) checkUpgrade2to3ResultForCloud("DROPBOX", accessToken, url, username, webdavCertificate) checkUpgrade2to3ResultForCloud("ONEDRIVE", accessToken, url, username, webdavCertificate) @@ -109,9 +122,9 @@ class UpgradeDatabaseTest { @Test fun upgrade3To4() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) val ids = arrayOf("10", "20", "31", "32", "51") @@ -126,7 +139,7 @@ class UpgradeDatabaseTest { .executeOn(db) } - Upgrade3To4().applyTo(db, 3) + Upgrade3To4().migrate(db) Sql.query("VAULT_ENTITY").where("CLOUD_TYPE", Sql.eq(CloudType.DROPBOX.name)).executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(ids.size)) @@ -144,10 +157,10 @@ class UpgradeDatabaseTest { @Test fun upgrade4To5() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) val cloudId = 15 val cloudUrl = "url" @@ -180,7 +193,7 @@ class UpgradeDatabaseTest { .integer("POSITION", position) // .executeOn(db) - Upgrade4To5().applyTo(db, 4) + Upgrade4To5().migrate(db) Sql.query("CLOUD_ENTITY").where("TYPE", Sql.eq(CloudType.WEBDAV.name)).executeOn(db).use { it.moveToFirst() @@ -206,11 +219,11 @@ class UpgradeDatabaseTest { @Test fun upgrade5To6() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) val cloudId = 15 val cloudUrl = "url" @@ -243,7 +256,7 @@ class UpgradeDatabaseTest { .integer("POSITION", position) // .executeOn(db) - Upgrade5To6().applyTo(db, 5) + Upgrade5To6().migrate(db) Sql.query("CLOUD_ENTITY").where("TYPE", Sql.eq(CloudType.WEBDAV.name)).executeOn(db).use { it.moveToFirst() @@ -269,12 +282,12 @@ class UpgradeDatabaseTest { @Test fun upgrade6To7() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) val licenseToken = "licenseToken" val releaseNote = "releaseNote" @@ -290,7 +303,7 @@ class UpgradeDatabaseTest { .set("URL_TO_RELEASE_NOTE", Sql.toString(urlReleaseNote)) .executeOn(db) - Upgrade6To7().applyTo(db, 6) + Upgrade6To7().migrate(db) Sql.query("UPDATE_CHECK_ENTITY").executeOn(db).use { it.moveToFirst() @@ -305,12 +318,12 @@ class UpgradeDatabaseTest { @Test fun recoverUpgrade6to7DueToSQLiteExceptionThrown() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) val licenseToken = "licenseToken" @@ -349,13 +362,13 @@ class UpgradeDatabaseTest { @Test fun upgrade7To8() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -383,7 +396,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(5)) } - Upgrade7To8().applyTo(db, 7) + Upgrade7To8().migrate(db) Sql.query("CLOUD_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(4)) @@ -396,33 +409,33 @@ class UpgradeDatabaseTest { @Test fun upgrade8To9() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) sharedPreferencesHandler.setBetaScreenDialogAlreadyShown(true) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) + Upgrade8To9(sharedPreferencesHandler).migrate(db) Assert.assertThat(sharedPreferencesHandler.isBetaModeAlreadyShown(), CoreMatchers.`is`(false)) } @Test fun upgrade9To10() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -460,7 +473,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(5)) } - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) + Upgrade9To10(sharedPreferencesHandler).migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -475,16 +488,16 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11EmptyOnedriveCloudRemovesCloud() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) Sql.insertInto("VAULT_ENTITY") // .integer("_id", 25) // @@ -500,7 +513,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(3)) } - Upgrade10To11().applyTo(db, 10) + Upgrade10To11().migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -525,16 +538,16 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11UsedOnedriveCloudPreservesCloud() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) Sql.insertInto("VAULT_ENTITY") // .integer("_id", 25) // @@ -559,7 +572,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(3)) } - Upgrade10To11().applyTo(db, 10) + Upgrade10To11().migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -584,65 +597,64 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12IfOldDefaultSet() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(7)) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade11To12(sharedPreferencesHandler).migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1))) } @Test fun upgrade11To12MonthlySet() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(30)) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade11To12(sharedPreferencesHandler).migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1))) } @Test fun upgrade11To12MonthlyNever() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) + Upgrade0To1().migrate(db) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.absent()) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade11To12(sharedPreferencesHandler).migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent())) } -} -*/ \ No newline at end of file +} \ No newline at end of file From 87ab5417e16dbe732c2b97e6cf3cce5735bbd259 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 4 Nov 2023 21:11:35 +0100 Subject: [PATCH 013/120] Reformatted UpgradeDatabaseTest --- .../data/db/UpgradeDatabaseTest.kt | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 99b63853d..da1fe97cd 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -84,20 +84,20 @@ class UpgradeDatabaseTest { val webdavCertificate = "webdavCertificate" val accessToken = "accessToken" - Sql.update("CLOUD_ENTITY") - .where("TYPE", Sql.eq("DROPBOX")) - .set("ACCESS_TOKEN", Sql.toString(accessToken)) - .set("WEBDAV_URL", Sql.toString(url)) - .set("USERNAME", Sql.toString(username)) - .set("WEBDAV_CERTIFICATE", Sql.toString(webdavCertificate)) + Sql.update("CLOUD_ENTITY") // + .where("TYPE", Sql.eq("DROPBOX")) // + .set("ACCESS_TOKEN", Sql.toString(accessToken)) // + .set("WEBDAV_URL", Sql.toString(url)) // + .set("USERNAME", Sql.toString(username)) // + .set("WEBDAV_CERTIFICATE", Sql.toString(webdavCertificate)) // .executeOn(db) - Sql.update("CLOUD_ENTITY") - .where("TYPE", Sql.eq("ONEDRIVE")) - .set("ACCESS_TOKEN", Sql.toString("NOT USED")) - .set("WEBDAV_URL", Sql.toString(url)) - .set("USERNAME", Sql.toString(username)) - .set("WEBDAV_CERTIFICATE", Sql.toString(webdavCertificate)) + Sql.update("CLOUD_ENTITY") // + .where("TYPE", Sql.eq("ONEDRIVE")) // + .set("ACCESS_TOKEN", Sql.toString("NOT USED")) // + .set("WEBDAV_URL", Sql.toString(url)) // + .set("USERNAME", Sql.toString(username)) // + .set("WEBDAV_CERTIFICATE", Sql.toString(webdavCertificate)) // .executeOn(db) context.getSharedPreferences("com.microsoft.live", Context.MODE_PRIVATE).edit().putString("refresh_token", accessToken).commit() @@ -295,12 +295,12 @@ class UpgradeDatabaseTest { val urlApk = "urlApk" val urlReleaseNote = "urlReleaseNote" - Sql.update("UPDATE_CHECK_ENTITY") - .set("LICENSE_TOKEN", Sql.toString(licenseToken)) - .set("RELEASE_NOTE", Sql.toString(releaseNote)) - .set("VERSION", Sql.toString(version)) - .set("URL_TO_APK", Sql.toString(urlApk)) - .set("URL_TO_RELEASE_NOTE", Sql.toString(urlReleaseNote)) + Sql.update("UPDATE_CHECK_ENTITY") // + .set("LICENSE_TOKEN", Sql.toString(licenseToken)) // + .set("RELEASE_NOTE", Sql.toString(releaseNote)) // + .set("VERSION", Sql.toString(version)) // + .set("URL_TO_APK", Sql.toString(urlApk)) // + .set("URL_TO_RELEASE_NOTE", Sql.toString(urlReleaseNote)) // .executeOn(db) Upgrade6To7().migrate(db) @@ -327,12 +327,12 @@ class UpgradeDatabaseTest { val licenseToken = "licenseToken" - Sql.update("UPDATE_CHECK_ENTITY") - .set("LICENSE_TOKEN", Sql.toString(licenseToken)) - .set("RELEASE_NOTE", Sql.toString("releaseNote")) - .set("VERSION", Sql.toString("version")) - .set("URL_TO_APK", Sql.toString("urlApk")) - .set("URL_TO_RELEASE_NOTE", Sql.toString("urlReleaseNote")) + Sql.update("UPDATE_CHECK_ENTITY") // + .set("LICENSE_TOKEN", Sql.toString(licenseToken)) // + .set("RELEASE_NOTE", Sql.toString("releaseNote")) // + .set("VERSION", Sql.toString("version")) // + .set("URL_TO_APK", Sql.toString("urlApk")) // + .set("URL_TO_RELEASE_NOTE", Sql.toString("urlReleaseNote")) // .executeOn(db) Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) @@ -376,7 +376,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .text("USERNAME", "username") // .text("WEBDAV_CERTIFICATE", "certificate") // - .text("ACCESS_TOKEN", "accessToken") + .text("ACCESS_TOKEN", "accessToken") // .text("S3_BUCKET", "s3Bucket") // .text("S3_REGION", "s3Region") // .text("S3_SECRET_KEY", "s3SecretKey") // @@ -443,7 +443,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .text("USERNAME", "username") // .text("WEBDAV_CERTIFICATE", "certificate") // - .text("ACCESS_TOKEN", "accessToken") + .text("ACCESS_TOKEN", "accessToken") // .text("S3_BUCKET", "s3Bucket") // .text("S3_REGION", "s3Region") // .text("S3_SECRET_KEY", "s3SecretKey") // @@ -561,10 +561,10 @@ class UpgradeDatabaseTest { Sql.query("CLOUD_ENTITY").executeOn(db).use { while (it.moveToNext()) { - Sql.update("CLOUD_ENTITY") - .where("_id", Sql.eq(3L)) - .set("ACCESS_TOKEN", Sql.toString("Access token 3000")) - .set("USERNAME", Sql.toString("foo@bar.baz")) + Sql.update("CLOUD_ENTITY") // + .where("_id", Sql.eq(3L)) // + .set("ACCESS_TOKEN", Sql.toString("Access token 3000")) // + .set("USERNAME", Sql.toString("foo@bar.baz")) // .executeOn(db) } } From af6a691b92eb5cc11721d85fe43474acbc38ad2b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:15:03 +0100 Subject: [PATCH 014/120] Fixed regression in Sql: Caller is now allowed to omit columns to receive all --- .../cryptomator/data/db/migrations/Sql.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index c34606fda..065e18612 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -125,16 +125,24 @@ public SqlQueryBuilder where(String column, Criterion criterion) { } public Cursor executeOn(SupportSQLiteDatabase db) { - if (columns == null || columns.isEmpty()) { - throw new IllegalArgumentException(); - } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException(); } - StringBuilder query = new StringBuilder().append("SELECT ("); - appendColumns(query, columns.toArray(new String[0]), null, false); - query.append(") FROM ").append('"').append(tableName).append('"').append(" WHERE ").append(whereClause); + StringBuilder query = new StringBuilder().append("SELECT "); + if (columns == null || columns.isEmpty()) { + query.append("*"); + } else { + query.append("("); + appendColumns(query, columns.toArray(new String[0]), null, false); + query.append(")"); + } + + query.append(" FROM ").append('"').append(tableName).append('"'); + + if (whereClause.length() > 0) { + query.append(" WHERE ").append(whereClause); + } return db.query(query.toString(), whereArgs.toArray()); } From 91d3e80af5f2a2a6a152320eaea1ecf5603f7858 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:19:00 +0100 Subject: [PATCH 015/120] Added and configured "room-testing" --- buildsystem/dependencies.gradle | 1 + data/build.gradle | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 114121738..b890d5f58 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -144,6 +144,7 @@ ext { trackingFreeGoogleAndroidCLient: files("lib/google-http-client-android-${trackingFreeGoogleCLientVersion}.jar"), room : "androidx.room:room-runtime:${roomVersion}", roomCompiler : "androidx.room:room-compiler:${roomVersion}", + roomTesting : "androidx.room:room-testing:${roomVersion}", gson : "com.google.code.gson:gson:${gsonVersion}", hamcrest : "org.hamcrest:hamcrest-all:${hamcrestVersion}", javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}", diff --git a/data/build.gradle b/data/build.gradle index b3b4f05de..26fe938ea 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'de.mannodermaus.android-junit5' +def roomSchemasDir = new File(projectDir, "room/schemas") android { def globalConfiguration = rootProject.extensions.getByName("ext") @@ -21,7 +22,7 @@ android { javaCompileOptions { annotationProcessorOptions { compilerArgumentProviders( - new RoomSchemaArgProvider(new File(projectDir, "room/schemas")) + new RoomSchemaArgProvider(roomSchemasDir) ) } } @@ -82,6 +83,8 @@ android { lite { java.srcDirs = ['src/main/java/', 'src/lite/java/'] } + + androidTest.assets.srcDirs += roomSchemasDir } packagingOptions { resources { @@ -223,6 +226,7 @@ dependencies { testImplementation dependencies.mockitoInline testImplementation dependencies.hamcrest + androidTestImplementation dependencies.roomTesting androidTestImplementation(dependencies.runner) { exclude group: 'com.android.support', module: 'support-annotations' } From 420a1d3ffe1783656cc515f93000f4b7af8c2964 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:51:34 +0100 Subject: [PATCH 016/120] Added first tests for migrations involving room using "room-testing" --- .../data/db/UpgradeDatabaseTest.kt | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index da1fe97cd..bef9100c9 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -1,6 +1,7 @@ package org.cryptomator.data.db import android.content.Context +import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory @@ -21,6 +22,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 +import org.cryptomator.data.db.migrations.manual.Migration12To13 import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CredentialCryptor @@ -28,20 +30,33 @@ import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Assert import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +private const val TEST_DB = "migration-test" +private const val LATEST_LEGACY_MIGRATION = 12 + @RunWith(AndroidJUnit4::class) @SmallTest class UpgradeDatabaseTest { - private val context = InstrumentationRegistry.getInstrumentation().context + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private val context = instrumentation.context private val sharedPreferencesHandler = SharedPreferencesHandler(context) + private lateinit var db: SupportSQLiteDatabase + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( // + instrumentation, // + CryptomatorDatabase::class.java, // + listOf() //TODO AutoSpecs + ) + @Before fun setup() { - db = SupportSQLiteOpenHelper.Configuration(context, null, object : SupportSQLiteOpenHelper.Callback(1) { + db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { override fun onCreate(db: SupportSQLiteDatabase) {} override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {} @@ -52,6 +67,8 @@ class UpgradeDatabaseTest { @After fun tearDown() { db.close() + //Room handles creating/deleting room-only databases correctly, but this falls apart when using the FrameworkSQLiteOpenHelper directly + context.getDatabasePath(TEST_DB).delete() } @Test @@ -67,10 +84,12 @@ class UpgradeDatabaseTest { Upgrade8To9(sharedPreferencesHandler).migrate(db) Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) + db.close() - /*CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll() - VaultEntityDao(DaoConfig(db, VaultEntityDao::class.java)).loadAll() - UpdateCheckEntityDao(DaoConfig(db, UpdateCheckEntityDao::class.java)).loadAll()*/ //TODO + helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()) + helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()) //TODO Use correctly + //TODO Verify content (e.g. by using "loadAll") } From a83e968120d0146c15ea2aad350337f9e1f8be94 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 18 Nov 2023 17:35:44 +0100 Subject: [PATCH 017/120] Changed timing of migrations --- data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 5790a0806..bfca6f7a9 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -35,6 +35,9 @@ class DatabaseModule { .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) .also { // + //Migrations are only triggered once the database is used for the first time. + //-- Let's do that now and verify all went well before returning the database. + require(it.openHelper.writableDatabase.version == CRYPTOMATOR_DATABASE_VERSION) Timber.tag("Database").i("Database built successfully") } } From 7e8461730093d89651cca5af4dad4daf8fe15b0b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:19:54 +0100 Subject: [PATCH 018/120] Refactored CloudRepositoryImpl to use CloudDao directly --- .../data/repository/CloudRepositoryImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index a9582cc8f..ad1233721 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -3,7 +3,7 @@ import com.google.common.base.Optional; import org.cryptomator.data.cloud.crypto.CryptoCloudFactory; -import org.cryptomator.data.db.CryptomatorDatabase; +import org.cryptomator.data.db.CloudDao; import org.cryptomator.data.db.mappers.CloudEntityMapper; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudFolder; @@ -24,7 +24,7 @@ @Singleton class CloudRepositoryImpl implements CloudRepository { - private final CryptomatorDatabase database; + private final CloudDao cloudDao; private final CryptoCloudFactory cryptoCloudFactory; private final CloudEntityMapper mapper; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -32,9 +32,9 @@ class CloudRepositoryImpl implements CloudRepository { @Inject public CloudRepositoryImpl(CloudEntityMapper mapper, // CryptoCloudFactory cryptoCloudFactory, // - CryptomatorDatabase database, // + CloudDao cloudDao, // DispatchingCloudContentRepository dispatchingCloudContentRepository) { - this.database = database; + this.cloudDao = cloudDao; this.cryptoCloudFactory = cryptoCloudFactory; this.mapper = mapper; this.dispatchingCloudContentRepository = dispatchingCloudContentRepository; @@ -43,7 +43,7 @@ public CloudRepositoryImpl(CloudEntityMapper mapper, // @Override public List clouds(CloudType cloudType) throws BackendException { List cloudsFromType = new ArrayList<>(); - List allClouds = mapper.fromEntities(database.cloudDao().loadAll()); + List allClouds = mapper.fromEntities(cloudDao.loadAll()); for (Cloud cloud : allClouds) { if (cloud.type().equals(cloudType)) { @@ -56,7 +56,7 @@ public List clouds(CloudType cloudType) throws BackendException { @Override public List allClouds() throws BackendException { - return mapper.fromEntities(database.cloudDao().loadAll()); + return mapper.fromEntities(cloudDao.loadAll()); } @Override @@ -65,7 +65,7 @@ public Cloud store(Cloud cloud) { throw new IllegalArgumentException("Can not store non persistent cloud"); } - Cloud storedCloud = mapper.fromEntity(database.cloudDao().storeReplacingAndReload(mapper.toEntity(cloud))); + Cloud storedCloud = mapper.fromEntity(cloudDao.storeReplacingAndReload(mapper.toEntity(cloud))); dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud); @@ -77,7 +77,7 @@ public void delete(Cloud cloud) { if (!cloud.persistent()) { throw new IllegalArgumentException("Can not delete non persistent cloud"); } - database.cloudDao().delete(mapper.toEntity(cloud)); + cloudDao.delete(mapper.toEntity(cloud)); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cloud); } From bc7f8a204c7a0ccac167df3a7122ae6b1cf56fbe Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:18:13 +0100 Subject: [PATCH 019/120] Refactored database to be initialized on demand --- .../org/cryptomator/data/db/DatabaseModule.kt | 13 +++++++------ .../data/db/mappers/VaultEntityMapper.java | 7 ++++--- .../data/repository/CloudRepositoryImpl.java | 13 +++++++------ .../repository/UpdateCheckRepositoryImpl.java | 17 +++++++++-------- .../data/repository/VaultRepositoryImpl.java | 13 +++++++------ 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index bfca6f7a9..7c602d8f7 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -18,6 +18,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 +import javax.inject.Provider import javax.inject.Singleton import dagger.Module import dagger.Provides @@ -44,20 +45,20 @@ class DatabaseModule { @Singleton @Provides - fun provideCloudDao(database: CryptomatorDatabase): CloudDao { - return database.cloudDao() + fun provideCloudDao(database: Provider): CloudDao { + return database.get().cloudDao() } @Singleton @Provides - fun provideUpdateCheckDao(database: CryptomatorDatabase): UpdateCheckDao { - return database.updateCheckDao() + fun provideUpdateCheckDao(database: Provider): UpdateCheckDao { + return database.get().updateCheckDao() } @Singleton @Provides - fun provideVaultDao(database: CryptomatorDatabase): VaultDao { - return database.vaultDao() + fun provideVaultDao(database: Provider): VaultDao { + return database.get().vaultDao() } @Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java index 59707ba69..0012d4aba 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java @@ -8,6 +8,7 @@ import org.cryptomator.domain.exception.BackendException; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; import static org.cryptomator.domain.Vault.aVault; @@ -16,10 +17,10 @@ public class VaultEntityMapper extends EntityMapper { private final CloudEntityMapper cloudEntityMapper; - private final CloudDao cloudDao; + private final Provider cloudDao; @Inject - public VaultEntityMapper(CloudEntityMapper cloudEntityMapper, CloudDao cloudDao) { + public VaultEntityMapper(CloudEntityMapper cloudEntityMapper, Provider cloudDao) { this.cloudDao = cloudDao; this.cloudEntityMapper = cloudEntityMapper; } @@ -43,7 +44,7 @@ private Cloud cloudFrom(VaultEntity entity) { if (entity.getFolderCloudId() == null) { return null; } - return cloudEntityMapper.fromEntity(cloudDao.load(entity.getFolderCloudId())); + return cloudEntityMapper.fromEntity(cloudDao.get().load(entity.getFolderCloudId())); } @Override diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index ad1233721..3e06f3249 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -19,12 +19,13 @@ import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; @Singleton class CloudRepositoryImpl implements CloudRepository { - private final CloudDao cloudDao; + private final Provider cloudDao; private final CryptoCloudFactory cryptoCloudFactory; private final CloudEntityMapper mapper; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -32,7 +33,7 @@ class CloudRepositoryImpl implements CloudRepository { @Inject public CloudRepositoryImpl(CloudEntityMapper mapper, // CryptoCloudFactory cryptoCloudFactory, // - CloudDao cloudDao, // + Provider cloudDao, // DispatchingCloudContentRepository dispatchingCloudContentRepository) { this.cloudDao = cloudDao; this.cryptoCloudFactory = cryptoCloudFactory; @@ -43,7 +44,7 @@ public CloudRepositoryImpl(CloudEntityMapper mapper, // @Override public List clouds(CloudType cloudType) throws BackendException { List cloudsFromType = new ArrayList<>(); - List allClouds = mapper.fromEntities(cloudDao.loadAll()); + List allClouds = mapper.fromEntities(cloudDao.get().loadAll()); for (Cloud cloud : allClouds) { if (cloud.type().equals(cloudType)) { @@ -56,7 +57,7 @@ public List clouds(CloudType cloudType) throws BackendException { @Override public List allClouds() throws BackendException { - return mapper.fromEntities(cloudDao.loadAll()); + return mapper.fromEntities(cloudDao.get().loadAll()); } @Override @@ -65,7 +66,7 @@ public Cloud store(Cloud cloud) { throw new IllegalArgumentException("Can not store non persistent cloud"); } - Cloud storedCloud = mapper.fromEntity(cloudDao.storeReplacingAndReload(mapper.toEntity(cloud))); + Cloud storedCloud = mapper.fromEntity(cloudDao.get().storeReplacingAndReload(mapper.toEntity(cloud))); dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud); @@ -77,7 +78,7 @@ public void delete(Cloud cloud) { if (!cloud.persistent()) { throw new IllegalArgumentException("Can not delete non persistent cloud"); } - cloudDao.delete(mapper.toEntity(cloud)); + cloudDao.get().delete(mapper.toEntity(cloud)); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cloud); } diff --git a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java index 28efa8d39..4c26c78e5 100644 --- a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java @@ -30,6 +30,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; import io.jsonwebtoken.Claims; @@ -46,12 +47,12 @@ public class UpdateCheckRepositoryImpl implements UpdateCheckRepository { private static final String HOSTNAME_LATEST_VERSION = "https://api.cryptomator.org/android/latest-version.json"; - private final UpdateCheckDao updateCheckDao; + private final Provider updateCheckDao; private final OkHttpClient httpClient; private final Context context; @Inject - UpdateCheckRepositoryImpl(UpdateCheckDao updateCheckDao, Context context) { + UpdateCheckRepositoryImpl(Provider updateCheckDao, Context context) { this.httpClient = httpClient(); this.updateCheckDao = updateCheckDao; this.context = context; @@ -71,7 +72,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back return Optional.absent(); } - final UpdateCheckEntity entity = updateCheckDao.load(1L); + final UpdateCheckEntity entity = updateCheckDao.get().load(1L); if (entity.getVersion() != null && entity.getVersion().equals(latestVersion.version) && entity.getApkSha256() != null) { return Optional.of(new UpdateCheckImpl("", entity)); @@ -82,7 +83,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back entity.setVersion(updateCheck.getVersion()); entity.setApkSha256(updateCheck.getApkSha256()); - updateCheckDao.storeReplacing(entity); + updateCheckDao.get().storeReplacing(entity); return Optional.of(updateCheck); } @@ -90,22 +91,22 @@ public Optional getUpdateCheck(final String appVersion) throws Back @Nullable @Override public String getLicense() { - return updateCheckDao.load(1L).getLicenseToken(); + return updateCheckDao.get().load(1L).getLicenseToken(); } @Override public void setLicense(String license) { - final UpdateCheckEntity entity = updateCheckDao.load(1L); + final UpdateCheckEntity entity = updateCheckDao.get().load(1L); entity.setLicenseToken(license); - updateCheckDao.storeReplacing(entity); + updateCheckDao.get().storeReplacing(entity); } @Override public void update(File file) throws GeneralUpdateErrorException { try { - final UpdateCheckEntity entity = updateCheckDao.load(1L); + final UpdateCheckEntity entity = updateCheckDao.get().load(1L); final Request request = new Request // .Builder() // diff --git a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java index bc6d23a45..e6fd59760 100644 --- a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java @@ -15,6 +15,7 @@ import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; import static org.cryptomator.domain.Vault.aCopyOf; @@ -22,7 +23,7 @@ @Singleton class VaultRepositoryImpl implements VaultRepository { - private final VaultDao vaultDao; + private final Provider vaultDao; private final VaultEntityMapper mapper; private final CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -34,7 +35,7 @@ public VaultRepositoryImpl( // CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory, // CryptoCloudFactory cryptoCloudFactory, // DispatchingCloudContentRepository dispatchingCloudContentRepository, // - VaultDao vaultDao) { + Provider vaultDao) { this.mapper = mapper; this.vaultDao = vaultDao; this.cryptoCloudContentRepositoryFactory = cryptoCloudContentRepositoryFactory; @@ -45,7 +46,7 @@ public VaultRepositoryImpl( // @Override public List vaults() throws BackendException { List result = new ArrayList<>(); - for (Vault vault : mapper.fromEntities(vaultDao.loadAll())) { + for (Vault vault : mapper.fromEntities(vaultDao.get().loadAll())) { result.add(aCopyOf(vault).withUnlocked(isUnlocked(vault)).build()); } return result; @@ -54,7 +55,7 @@ public List vaults() throws BackendException { @Override public Vault store(Vault vault) throws BackendException { try { - return mapper.fromEntity(vaultDao.storeReplacingAndReload(mapper.toEntity(vault))); + return mapper.fromEntity(vaultDao.get().storeReplacingAndReload(mapper.toEntity(vault))); } catch (SQLiteConstraintException e) { throw new VaultAlreadyExistException(); } @@ -64,13 +65,13 @@ public Vault store(Vault vault) throws BackendException { public Long delete(Vault vault) throws BackendException { deregisterUnlocked(vault); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cryptoCloudFactory.decryptedViewOf(vault)); - vaultDao.delete(mapper.toEntity(vault)); + vaultDao.get().delete(mapper.toEntity(vault)); return vault.getId(); } @Override public Vault load(Long id) throws BackendException { - Vault vault = mapper.fromEntity(vaultDao.load(id)); + Vault vault = mapper.fromEntity(vaultDao.get().load(id)); return aCopyOf(vault).withUnlocked(isUnlocked(vault)).build(); } From 9423a871744c8cdb6a2afae6d8480ed8349ffe1c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 18 Nov 2023 17:35:35 +0100 Subject: [PATCH 020/120] Changed configuration to use template db for creation of app db Without this change room will create the app db itself on installation without running the migrations. --- data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db | 1 + .../src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db diff --git a/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db b/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db new file mode 100644 index 000000000..af0baec87 --- /dev/null +++ b/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 7c602d8f7..4654d0706 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -5,7 +5,6 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 @@ -24,6 +23,8 @@ import dagger.Module import dagger.Provides import timber.log.Timber +private const val BASE_DATABASE_ASSET = "databases/legacy/Cryptomator_DB_v1.db" + @Module class DatabaseModule { @@ -32,6 +33,7 @@ class DatabaseModule { fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // + .createFromAsset(BASE_DATABASE_ASSET) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) @@ -64,7 +66,6 @@ class DatabaseModule { @Singleton @Provides internal fun provideMigrations( - upgrade0To1: Upgrade0To1, // upgrade1To2: Upgrade1To2, // upgrade2To3: Upgrade2To3, // upgrade3To4: Upgrade3To4, // @@ -79,7 +80,6 @@ class DatabaseModule { // migration12To13: Migration12To13, // ): Array = arrayOf( - upgrade0To1, upgrade1To2, upgrade2To3, upgrade5To6, From bfbff577dd629519263998792a5b0f4cce792e26 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:16:28 +0100 Subject: [PATCH 021/120] Updated tests to use template db --- .../data/db/UpgradeDatabaseTest.kt | 49 ++++++++++++------- .../org/cryptomator/data/db/DatabaseModule.kt | 7 ++- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index bef9100c9..40a48703b 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -10,7 +10,6 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.google.common.base.Optional import org.cryptomator.data.db.migrations.Sql -import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 @@ -29,10 +28,13 @@ import org.cryptomator.util.crypto.CredentialCryptor import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import okio.use private const val TEST_DB = "migration-test" private const val LATEST_LEGACY_MIGRATION = 12 @@ -56,11 +58,20 @@ class UpgradeDatabaseTest { @Before fun setup() { + context.assets.open(DatabaseModule.BASE_DATABASE_ASSET).use { originStream -> + context.getDatabasePath(TEST_DB).outputStream().use { targetStream -> + originStream.copyTo(targetStream) + } + } db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { - override fun onCreate(db: SupportSQLiteDatabase) {} - - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {} + override fun onCreate(db: SupportSQLiteDatabase) { + fail("Database should not be created, but copied from asset") + } + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + assertEquals(1, oldVersion) + assertEquals(LATEST_LEGACY_MIGRATION, newVersion) + } }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } } @@ -73,7 +84,7 @@ class UpgradeDatabaseTest { @Test fun upgradeAll() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -95,7 +106,7 @@ class UpgradeDatabaseTest { @Test fun upgrade2To3() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) val url = "url" @@ -141,7 +152,7 @@ class UpgradeDatabaseTest { @Test fun upgrade3To4() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) @@ -176,7 +187,7 @@ class UpgradeDatabaseTest { @Test fun upgrade4To5() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -238,7 +249,7 @@ class UpgradeDatabaseTest { @Test fun upgrade5To6() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -301,7 +312,7 @@ class UpgradeDatabaseTest { @Test fun upgrade6To7() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -337,7 +348,7 @@ class UpgradeDatabaseTest { @Test fun recoverUpgrade6to7DueToSQLiteExceptionThrown() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -381,7 +392,7 @@ class UpgradeDatabaseTest { @Test fun upgrade7To8() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -428,7 +439,7 @@ class UpgradeDatabaseTest { @Test fun upgrade8To9() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -446,7 +457,7 @@ class UpgradeDatabaseTest { @Test fun upgrade9To10() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -507,7 +518,7 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11EmptyOnedriveCloudRemovesCloud() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -557,7 +568,7 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11UsedOnedriveCloudPreservesCloud() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -616,7 +627,7 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12IfOldDefaultSet() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -637,7 +648,7 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12MonthlySet() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -658,7 +669,7 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12MonthlyNever() { - Upgrade0To1().migrate(db) + //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 4654d0706..218e0babb 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -23,8 +23,6 @@ import dagger.Module import dagger.Provides import timber.log.Timber -private const val BASE_DATABASE_ASSET = "databases/legacy/Cryptomator_DB_v1.db" - @Module class DatabaseModule { @@ -94,6 +92,11 @@ class DatabaseModule { // migration12To13, ) + + companion object { + + const val BASE_DATABASE_ASSET = "databases/legacy/Cryptomator_DB_v1.db" + } } object DatabaseCallback : RoomDatabase.Callback() { From f40a147b9fa81ff54d0ca25d89102903bc45ff87 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:56:15 +0100 Subject: [PATCH 022/120] Replaced asset-based template with runtime-generated file --- .../databases/legacy/Cryptomator_DB_v1.db | 1 - .../org/cryptomator/data/db/DatabaseModule.kt | 36 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) delete mode 100644 data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db diff --git a/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db b/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db deleted file mode 100644 index af0baec87..000000000 --- a/data/src/main/assets/databases/legacy/Cryptomator_DB_v1.db +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 218e0babb..e5fb38c52 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -1,10 +1,14 @@ package org.cryptomator.data.db import android.content.Context +import android.content.ContextWrapper import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 @@ -17,6 +21,9 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.util.named +import java.io.File +import java.nio.file.Files import javax.inject.Provider import javax.inject.Singleton import dagger.Module @@ -26,12 +33,15 @@ import timber.log.Timber @Module class DatabaseModule { + private val DATABASE_NAME = "Cryptomator" + private val LOG = Timber.Forest.named("DatabaseModule") + @Singleton @Provides fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) - return Room.databaseBuilder(context, CryptomatorDatabase::class.java, "Cryptomator") // - .createFromAsset(BASE_DATABASE_ASSET) // + return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // + .createFromFile(createDbTemplate(context)) .addMigrations(*migrations) // .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) @@ -43,6 +53,28 @@ class DatabaseModule { } } + private fun createDbTemplate(context: Context): File { + val delegatingContext = object : ContextWrapper(context) { + override fun getDatabasePath(name: String?): File { + return Files.createTempDirectory(context.cacheDir.toPath(), "BaseDb").resolve(name).toFile() + } + } + val db = SupportSQLiteOpenHelper.Configuration.builder(delegatingContext) // + .name(DATABASE_NAME) // + .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) { + Upgrade0To1().migrate(db) + } + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = Unit + }).build() + .let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + require(db.version == 1) + db.close() + + return File(requireNotNull(db.path)) + } + @Singleton @Provides fun provideCloudDao(database: Provider): CloudDao { From 3e619a18e4b24b6c504a31abe6826d3097cdb1a4 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:17:07 +0100 Subject: [PATCH 023/120] Wrapped runtime-generated template with lazy stream --- .../main/java/org/cryptomator/data/db/DatabaseModule.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index e5fb38c52..c288a48f4 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -23,7 +23,9 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 import org.cryptomator.util.named import java.io.File +import java.io.InputStream import java.nio.file.Files +import java.util.concurrent.Callable import javax.inject.Provider import javax.inject.Singleton import dagger.Module @@ -41,7 +43,7 @@ class DatabaseModule { fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // - .createFromFile(createDbTemplate(context)) + .createFromInputStream(createTemplateInputStream(context)) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) @@ -53,6 +55,10 @@ class DatabaseModule { } } + private fun createTemplateInputStream(context: Context): Callable { + return Callable { createDbTemplate(context).inputStream() } + } + private fun createDbTemplate(context: Context): File { val delegatingContext = object : ContextWrapper(context) { override fun getDatabasePath(name: String?): File { From 4091a5107fbb5b9cdd9d582be65a023ce4c8c12e Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:21:38 +0100 Subject: [PATCH 024/120] Added template generation to dagger graph --- .../org/cryptomator/data/db/DatabaseModule.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index c288a48f4..78ce1c395 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -28,6 +28,7 @@ import java.nio.file.Files import java.util.concurrent.Callable import javax.inject.Provider import javax.inject.Singleton +import dagger.Lazy import dagger.Module import dagger.Provides import timber.log.Timber @@ -40,10 +41,10 @@ class DatabaseModule { @Singleton @Provides - fun provideCryptomatorDatabase(context: Context, migrations: Array): CryptomatorDatabase { + fun provideCryptomatorDatabase(context: Context, migrations: Array, dbTemplateStreamCallable: Callable): CryptomatorDatabase { Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // - .createFromInputStream(createTemplateInputStream(context)) // + .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // .build() //Fails if no migration is found (especially when downgrading) @@ -55,14 +56,21 @@ class DatabaseModule { } } - private fun createTemplateInputStream(context: Context): Callable { - return Callable { createDbTemplate(context).inputStream() } + @Singleton + @Provides + fun provideDbTemplateStreamCallable(dbTemplateFile: Lazy): Callable = Callable { + LOG.d("Creating database template stream") + return@Callable dbTemplateFile.get().inputStream() } - private fun createDbTemplate(context: Context): File { + @Singleton + @Provides + fun provideDbTemplateFile(context: Context): File { + LOG.d("Creating database template file") val delegatingContext = object : ContextWrapper(context) { override fun getDatabasePath(name: String?): File { - return Files.createTempDirectory(context.cacheDir.toPath(), "BaseDb").resolve(name).toFile() + require(name == DATABASE_NAME) + return Files.createTempDirectory(context.cacheDir.toPath(), "DbTemplate").resolve(name).toFile() } } val db = SupportSQLiteOpenHelper.Configuration.builder(delegatingContext) // @@ -78,6 +86,7 @@ class DatabaseModule { require(db.version == 1) db.close() + LOG.d("Created database template file") return File(requireNotNull(db.path)) } From 9c6ec190b28698694bca6e9d8dd544f2ae1b6e75 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:41:41 +0100 Subject: [PATCH 025/120] Cleaned up DatabaseModule Used dedicated logger Added "DbInternal" qualifier to not pollute the dagger graph Reformatted file --- .../org/cryptomator/data/db/DatabaseModule.kt | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 78ce1c395..bda6dee65 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -27,22 +27,23 @@ import java.io.InputStream import java.nio.file.Files import java.util.concurrent.Callable import javax.inject.Provider +import javax.inject.Qualifier import javax.inject.Singleton import dagger.Lazy import dagger.Module import dagger.Provides import timber.log.Timber +private val DATABASE_NAME = "Cryptomator" +private val LOG = Timber.Forest.named("DatabaseModule") + @Module class DatabaseModule { - private val DATABASE_NAME = "Cryptomator" - private val LOG = Timber.Forest.named("DatabaseModule") - @Singleton @Provides - fun provideCryptomatorDatabase(context: Context, migrations: Array, dbTemplateStreamCallable: Callable): CryptomatorDatabase { - Timber.tag("Database").i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) + fun provideCryptomatorDatabase(context: Context, @DbInternal migrations: Array, @DbInternal dbTemplateStreamCallable: Callable): CryptomatorDatabase { + LOG.i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // @@ -52,19 +53,21 @@ class DatabaseModule { //Migrations are only triggered once the database is used for the first time. //-- Let's do that now and verify all went well before returning the database. require(it.openHelper.writableDatabase.version == CRYPTOMATOR_DATABASE_VERSION) - Timber.tag("Database").i("Database built successfully") + LOG.i("Database built successfully") } } @Singleton @Provides - fun provideDbTemplateStreamCallable(dbTemplateFile: Lazy): Callable = Callable { + @DbInternal + fun provideDbTemplateStreamCallable(@DbInternal dbTemplateFile: Lazy): Callable = Callable { LOG.d("Creating database template stream") return@Callable dbTemplateFile.get().inputStream() } @Singleton @Provides + @DbInternal fun provideDbTemplateFile(context: Context): File { LOG.d("Creating database template file") val delegatingContext = object : ContextWrapper(context) { @@ -81,8 +84,7 @@ class DatabaseModule { } override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = Unit - }).build() - .let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + }).build().let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } require(db.version == 1) db.close() @@ -110,6 +112,7 @@ class DatabaseModule { @Singleton @Provides + @DbInternal internal fun provideMigrations( upgrade1To2: Upgrade1To2, // upgrade2To3: Upgrade2To3, // @@ -149,15 +152,20 @@ class DatabaseModule { object DatabaseCallback : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { - Timber.tag("Database").i("Created database (v%s)", db.version) + LOG.i("Created database (v%s)", db.version) } override fun onOpen(db: SupportSQLiteDatabase) { - Timber.tag("Database").i("Opened database (v%s)", db.version) + LOG.i("Opened database (v%s)", db.version) } override fun onDestructiveMigration(db: SupportSQLiteDatabase) { //This should not be called throw UnsupportedOperationException("Destructive migration is not supported") } -} \ No newline at end of file +} + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +private annotation class DbInternal \ No newline at end of file From 9e11d77ab867eaf9077bbad364644b268be41b18 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:31:47 +0100 Subject: [PATCH 026/120] Added warning if database is built on main thread --- .../org/cryptomator/data/db/DatabaseModule.kt | 3 +++ .../java/org/cryptomator/util/ThreadUtil.kt | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 util/src/main/java/org/cryptomator/util/ThreadUtil.kt diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index bda6dee65..1f3ea8af3 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -21,6 +21,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named import java.io.File import java.io.InputStream @@ -44,6 +45,7 @@ class DatabaseModule { @Provides fun provideCryptomatorDatabase(context: Context, @DbInternal migrations: Array, @DbInternal dbTemplateStreamCallable: Callable): CryptomatorDatabase { LOG.i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) + ThreadUtil.assumeNotMainThread() return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // @@ -70,6 +72,7 @@ class DatabaseModule { @DbInternal fun provideDbTemplateFile(context: Context): File { LOG.d("Creating database template file") + ThreadUtil.assumeNotMainThread() val delegatingContext = object : ContextWrapper(context) { override fun getDatabasePath(name: String?): File { require(name == DATABASE_NAME) diff --git a/util/src/main/java/org/cryptomator/util/ThreadUtil.kt b/util/src/main/java/org/cryptomator/util/ThreadUtil.kt new file mode 100644 index 000000000..7e4be646a --- /dev/null +++ b/util/src/main/java/org/cryptomator/util/ThreadUtil.kt @@ -0,0 +1,20 @@ +package org.cryptomator.util + +import android.os.Looper +import timber.log.Timber + +object ThreadUtil { + + val isMainThread: Boolean + get() = Looper.getMainLooper().isCurrentThread + + fun assertNotMainThread() { + check(!isMainThread) { "Error: Currently executing on main thread; aborting" } + } + + fun assumeNotMainThread() { + if (isMainThread) { + Timber.tag("ThreadUtil").w(Exception(), "Warning: Currently executing on main thread") + } + } +} \ No newline at end of file From bd9e23a85a72b9f4b6de589e842572617092b3e7 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 3 Dec 2023 21:19:59 +0100 Subject: [PATCH 027/120] Extracted TemplateDatabaseContext from object --- .../org/cryptomator/data/db/DatabaseModule.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 1f3ea8af3..eef4a8f90 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -27,6 +27,7 @@ import java.io.File import java.io.InputStream import java.nio.file.Files import java.util.concurrent.Callable +import javax.inject.Inject import javax.inject.Provider import javax.inject.Qualifier import javax.inject.Singleton @@ -70,16 +71,10 @@ class DatabaseModule { @Singleton @Provides @DbInternal - fun provideDbTemplateFile(context: Context): File { + fun provideDbTemplateFile(templateDatabaseContext: TemplateDatabaseContext): File { LOG.d("Creating database template file") ThreadUtil.assumeNotMainThread() - val delegatingContext = object : ContextWrapper(context) { - override fun getDatabasePath(name: String?): File { - require(name == DATABASE_NAME) - return Files.createTempDirectory(context.cacheDir.toPath(), "DbTemplate").resolve(name).toFile() - } - } - val db = SupportSQLiteOpenHelper.Configuration.builder(delegatingContext) // + val db = SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { override fun onCreate(db: SupportSQLiteDatabase) { @@ -152,6 +147,15 @@ class DatabaseModule { } } +@Singleton +class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWrapper(context) { + + override fun getDatabasePath(name: String?): File { + require(name == DATABASE_NAME) + return Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() + } +} + object DatabaseCallback : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { From 386aa53d17bb702cbefbcbe5a1c1ba37912ce91b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:19:03 +0100 Subject: [PATCH 028/120] Added tests and changed contract of TemplateDatabaseContext --- .../cryptomator/data/db/CreateDatabaseTest.kt | 46 +++++++++++++++++++ .../data/db/TemplateDatabaseContextTest.kt | 35 ++++++++++++++ .../org/cryptomator/data/db/DatabaseModule.kt | 6 ++- .../cryptomator/data/db/migrations/Sql.java | 7 +++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt new file mode 100644 index 000000000..980902a1c --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -0,0 +1,46 @@ +package org.cryptomator.data.db + +import androidx.room.util.useCursor +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import org.cryptomator.data.db.migrations.Sql +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class CreateDatabaseTest { + + private val context = InstrumentationRegistry.getInstrumentation().context + + @Test + fun testProvideDbTemplateFile() { + val templateDatabaseContext = TemplateDatabaseContext(context) + val templateFile = DatabaseModule().provideDbTemplateFile(templateDatabaseContext) + assertTrue(templateFile.exists()) + + val templateDb = SupportSQLiteOpenHelper.Configuration(templateDatabaseContext, DATABASE_NAME, object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = fail("Database should already be target version") + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + assertEquals(1, templateDb.version) + + val elements = mutableListOf("CLOUD_ENTITY", "VAULT_ENTITY", "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") + Sql.query("sqlite_master") // + .columns(listOf("name")) // + .where("name", Sql.notEq("android_metadata")) // + .executeOn(templateDb) // + .useCursor { + while (it.moveToNext()) { + val elementName = it.getString(it.getColumnIndex("name")) + assertTrue("Unknown/Duplicate element: \"$elementName\"", elements.remove(elementName)) + } + assertTrue("Missing element(s): ${elements.joinToString(prefix = "\"", postfix = "\"")}", elements.isEmpty()) + } + } +} \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt new file mode 100644 index 000000000..4bc848196 --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt @@ -0,0 +1,35 @@ +package org.cryptomator.data.db + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertSame +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class TemplateDatabaseContextTest { + + private val baseContext = InstrumentationRegistry.getInstrumentation().context + + @Test(expected = IllegalArgumentException::class) + fun testTempDatabaseContextIllegalName() { + TemplateDatabaseContext(baseContext).getDatabasePath("Database42") + } + + @Test + fun testTempDatabaseContext() { + val templateDbContext = TemplateDatabaseContext(baseContext) + val templatePath = templateDbContext.getDatabasePath(DATABASE_NAME) + templatePath.parentFile.let { tempDir -> + assertNotNull(tempDir) + assertEquals(baseContext.cacheDir, tempDir!!.parentFile) + } + + val secondInvocation = templateDbContext.getDatabasePath(DATABASE_NAME) + assertSame(templatePath, secondInvocation) + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index eef4a8f90..e6eb5c697 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -150,9 +150,13 @@ class DatabaseModule { @Singleton class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWrapper(context) { + private val dbFile: File by lazy { + return@lazy Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() + } + override fun getDatabasePath(name: String?): File { require(name == DATABASE_NAME) - return Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() + return dbFile } } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 065e18612..490881f2d 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -64,6 +64,13 @@ public static Criterion eq(final String value) { }; } + public static Criterion notEq(final String value) { + return (column, whereClause, whereArgs) -> { + whereClause.append('"').append(column).append("\" != ?"); + whereArgs.add(value); + }; + } + public static Criterion isNull() { return (column, whereClause, whereArgs) -> whereClause.append('"').append(column).append("\" IS NULL"); } From 802a2a12c43558f93217fb951d0aa81fe105ec34 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 3 Dec 2023 21:16:53 +0100 Subject: [PATCH 029/120] Updated tests for migrations to use new template db mechanism --- .../data/db/UpgradeDatabaseTest.kt | 25 ++++--------------- .../org/cryptomator/data/db/DatabaseModule.kt | 5 ---- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 40a48703b..65771e22b 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -47,6 +47,10 @@ class UpgradeDatabaseTest { private val context = instrumentation.context private val sharedPreferencesHandler = SharedPreferencesHandler(context) + private val templateDbFile = DatabaseModule().provideDbTemplateFile(TemplateDatabaseContext(context)).also { + it.deleteOnExit() + } + private lateinit var db: SupportSQLiteDatabase @get:Rule @@ -58,11 +62,7 @@ class UpgradeDatabaseTest { @Before fun setup() { - context.assets.open(DatabaseModule.BASE_DATABASE_ASSET).use { originStream -> - context.getDatabasePath(TEST_DB).outputStream().use { targetStream -> - originStream.copyTo(targetStream) - } - } + templateDbFile.copyTo(context.getDatabasePath(TEST_DB)) db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { override fun onCreate(db: SupportSQLiteDatabase) { fail("Database should not be created, but copied from asset") @@ -84,7 +84,6 @@ class UpgradeDatabaseTest { @Test fun upgradeAll() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -106,7 +105,6 @@ class UpgradeDatabaseTest { @Test fun upgrade2To3() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) val url = "url" @@ -152,7 +150,6 @@ class UpgradeDatabaseTest { @Test fun upgrade3To4() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) @@ -187,7 +184,6 @@ class UpgradeDatabaseTest { @Test fun upgrade4To5() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -249,7 +245,6 @@ class UpgradeDatabaseTest { @Test fun upgrade5To6() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -312,7 +307,6 @@ class UpgradeDatabaseTest { @Test fun upgrade6To7() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -348,7 +342,6 @@ class UpgradeDatabaseTest { @Test fun recoverUpgrade6to7DueToSQLiteExceptionThrown() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -392,7 +385,6 @@ class UpgradeDatabaseTest { @Test fun upgrade7To8() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -439,7 +431,6 @@ class UpgradeDatabaseTest { @Test fun upgrade8To9() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -457,7 +448,6 @@ class UpgradeDatabaseTest { @Test fun upgrade9To10() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -518,7 +508,6 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11EmptyOnedriveCloudRemovesCloud() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -568,7 +557,6 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11UsedOnedriveCloudPreservesCloud() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -627,7 +615,6 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12IfOldDefaultSet() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -648,7 +635,6 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12MonthlySet() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -669,7 +655,6 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12MonthlyNever() { - //Upgrade0To1().migrate(db) Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index e6eb5c697..449356de5 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -140,11 +140,6 @@ class DatabaseModule { // migration12To13, ) - - companion object { - - const val BASE_DATABASE_ASSET = "databases/legacy/Cryptomator_DB_v1.db" - } } @Singleton From fb76f1694ee58f0046929b0c06432731e50758ec Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 3 Dec 2023 21:18:31 +0100 Subject: [PATCH 030/120] Added exception and performed minor cleanup --- .../main/java/org/cryptomator/data/db/CryptomatorDatabase.kt | 1 + data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index ad38ec5f5..4f191a6fe 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -8,6 +8,7 @@ import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 +const val DATABASE_NAME = "Cryptomator" const val CRYPTOMATOR_DATABASE_VERSION = 14 @Database( diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 449356de5..c4835e1f6 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -36,7 +36,6 @@ import dagger.Module import dagger.Provides import timber.log.Timber -private val DATABASE_NAME = "Cryptomator" private val LOG = Timber.Forest.named("DatabaseModule") @Module @@ -81,7 +80,9 @@ class DatabaseModule { Upgrade0To1().migrate(db) } - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = Unit + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + throw IllegalStateException("Template may not be upgraded") + } }).build().let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } require(db.version == 1) db.close() From 32fe9fc8fb746eebd60db036e4139f5e5a598cbd Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:39:57 +0100 Subject: [PATCH 031/120] Separated room schemas from androidTest assets --- data/build.gradle | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/data/build.gradle b/data/build.gradle index 26fe938ea..002c8065e 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -4,6 +4,12 @@ apply plugin: 'kotlin-kapt' apply plugin: 'de.mannodermaus.android-junit5' def roomSchemasDir = new File(projectDir, "room/schemas") +def androidTestRoomSchemasDir = new File(buildDir, "tmp/room/schemas/androidTest") +def copyRoomSchemasTask = tasks.register("copyRoomSchemasToAndroidTestAssets", Sync.class) { + from(roomSchemasDir) + into(androidTestRoomSchemasDir) +} + android { def globalConfiguration = rootProject.extensions.getByName("ext") @@ -84,13 +90,24 @@ android { java.srcDirs = ['src/main/java/', 'src/lite/java/'] } - androidTest.assets.srcDirs += roomSchemasDir + androidTest.assets.srcDir(androidTestRoomSchemasDir) } packagingOptions { resources { excludes += ['META-INF/DEPENDENCIES', 'META-INF/NOTICE.md', 'META-INF/INDEX.LIST'] } } + libraryVariants.configureEach { variant -> + tasks.findByName("kapt${variant.name.capitalize()}Kotlin")?.configure { kaptTask -> + copyRoomSchemasTask.configure { it.mustRunAfter(kaptTask) } + } + tasks.findByName("kapt${variant.name.capitalize()}AndroidTestKotlin")?.configure { kaptTask -> + copyRoomSchemasTask.configure { it.mustRunAfter(kaptTask) } + } + tasks.findByName("merge${variant.name.capitalize()}AndroidTestAssets")?.configure { + it.dependsOn(copyRoomSchemasTask) + } + } lint { abortOnError false From 3d62a48eac178a79501035b1b9347bee7e529472 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:40:21 +0100 Subject: [PATCH 032/120] Changed action for foreign key in "VaultEntity" to "RESTRICT" --- .../org.cryptomator.data.db.CryptomatorDatabase/14.json | 8 ++++---- .../java/org/cryptomator/data/db/entities/VaultEntity.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json index 094c0d991..4ef814a19 100644 --- a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 14, - "identityHash": "a2a147e1f1c849da62d2c334c3c3d49a", + "identityHash": "5a294fd8ee96abf919ac6097dcab3306", "entities": [ { "tableName": "CLOUD_ENTITY", @@ -130,7 +130,7 @@ }, { "tableName": "VAULT_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `folderCloudId` INTEGER, `folderPath` TEXT, `folderName` TEXT, `cloudType` TEXT NOT NULL, `password` TEXT, `position` INTEGER, `format` INTEGER, `shorteningThreshold` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`folderCloudId`) REFERENCES `CLOUD_ENTITY`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `folderCloudId` INTEGER, `folderPath` TEXT, `folderName` TEXT, `cloudType` TEXT NOT NULL, `password` TEXT, `position` INTEGER, `format` INTEGER, `shorteningThreshold` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`folderCloudId`) REFERENCES `CLOUD_ENTITY`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", "fields": [ { "fieldPath": "id", @@ -208,7 +208,7 @@ "foreignKeys": [ { "table": "CLOUD_ENTITY", - "onDelete": "SET NULL", + "onDelete": "RESTRICT", "onUpdate": "NO ACTION", "columns": [ "folderCloudId" @@ -223,7 +223,7 @@ "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, 'a2a147e1f1c849da62d2c334c3c3d49a')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a294fd8ee96abf919ac6097dcab3306')" ] } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index faf8f7b12..50b32e021 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -8,7 +8,7 @@ import androidx.room.PrimaryKey @Entity( tableName = "VAULT_ENTITY", // indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["folderPath", "folderCloudId"], unique = true)], // - foreignKeys = [ForeignKey(CloudEntity::class, ["id"], ["folderCloudId"], onDelete = ForeignKey.SET_NULL)] + foreignKeys = [ForeignKey(CloudEntity::class, ["id"], ["folderCloudId"], onDelete = ForeignKey.RESTRICT)] ) data class VaultEntity constructor( @PrimaryKey override val id: Long?, From b7d2073c4475df64ccf080def93e704fd600b80a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:25:18 +0100 Subject: [PATCH 033/120] Cleaned up and improved implementations in "Sql.java" --- .../cryptomator/data/db/migrations/Sql.java | 123 ++++++++++-------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 490881f2d..2857bcc76 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -3,6 +3,7 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; import androidx.sqlite.db.SupportSQLiteDatabase; @@ -16,8 +17,6 @@ import static org.cryptomator.data.db.migrations.Sql.SqlCreateTableBuilder.ColumnType.TEXT; import static java.lang.String.format; -//TODO Use precompiled statements for all? -//TODO Compatibility of Rename table with new androids //https://developer.android.com/reference/android/database/sqlite/package-summary.html -- API 26 -> 3.18 public class Sql { @@ -113,6 +112,9 @@ public static class SqlQueryBuilder { private final List whereArgs = new ArrayList<>(); private List columns = new ArrayList<>(); + private String groupBy; + private String having; + private String limit; public SqlQueryBuilder(String tableName) { this.tableName = tableName; @@ -131,27 +133,38 @@ public SqlQueryBuilder where(String column, Criterion criterion) { return this; } - public Cursor executeOn(SupportSQLiteDatabase db) { - if (tableName == null || tableName.trim().isEmpty()) { - throw new IllegalArgumentException(); - } + public SqlQueryBuilder groupBy(String groupBy) { + this.groupBy = groupBy; + return this; + } - StringBuilder query = new StringBuilder().append("SELECT "); - if (columns == null || columns.isEmpty()) { - query.append("*"); - } else { - query.append("("); - appendColumns(query, columns.toArray(new String[0]), null, false); - query.append(")"); - } + public SqlQueryBuilder having(String having) { + this.having = having; + return this; + } - query.append(" FROM ").append('"').append(tableName).append('"'); + public SqlQueryBuilder limit(String limit) { + this.limit = limit; + return this; + } - if (whereClause.length() > 0) { - query.append(" WHERE ").append(whereClause); + public Cursor executeOn(SupportSQLiteDatabase db) { + if (tableName == null || tableName.trim().isEmpty()) { + throw new IllegalArgumentException(); } - - return db.query(query.toString(), whereArgs.toArray()); + String query = SQLiteQueryBuilder.buildQueryString( // + /* distinct */ false, // + tableName, // + columns.toArray(new String[columns.size()]), // + whereClause.toString(), // + groupBy, // + having, // + /* orderBy */ null, // + limit // + ); + //In contrast to "SupportSQLiteDatabase#update" "query" doesn't define how the contents of "whereArgs" are bound. + //As of now we always pass an "Array", but this has to be kept in mind if we ever change this. See: "SqlUpdateBuilder#executeOn" + return db.query(query, whereArgs.toArray()); } } @@ -184,11 +197,14 @@ public void executeOn(SupportSQLiteDatabase db) { if (contentValues.size() == 0) { throw new IllegalStateException("At least one value must be set"); } - //TODO Error handling in replacement of SQLiteDatabase#insertOrThrow, all-null-handling - // ... Handling of everything that was changed since the prior implementation - db.update(tableName, SQLiteDatabase.CONFLICT_FAIL, contentValues, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); - } + //The behavior of "SupportSQLiteDatabase#update" is a bit strange, which caused me to investigate: + //The docs say that the contents of "whereArgs" are bound as "Strings", even if the parameter is of type "Array". + //The internal binding methods are type-safe, but resolve to just putting all args into an "Array" in "SQLiteProgram" anyway. + //This array is also used by "SQLiteDatabase#update". Apparently the contents of the array are then bound as "Strings". + //As of now we always pass an "Array", but all of this has to be kept in mind if we ever change this. + db.update(tableName, SQLiteDatabase.CONFLICT_NONE, contentValues, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); + } } public static class SqlDropIndexBuilder { @@ -268,6 +284,7 @@ public void executeOn(SupportSQLiteDatabase db) { public static class SqlInsertSelectBuilder { + private static final int NOT_FOUND = -1; private final String table; private final String[] selectedColumns; private final StringBuilder joinClauses = new StringBuilder(); @@ -287,14 +304,33 @@ public SqlInsertSelectBuilder from(String sourceTableName) { public void executeOn(SupportSQLiteDatabase db) { StringBuilder query = new StringBuilder().append("INSERT INTO \"").append(table).append("\" ("); - appendColumns(query, columns, sourceTableName, false); + appendColumns(query, columns, false); query.append(") SELECT "); - appendColumns(query, selectedColumns, sourceTableName, true); + appendColumns(query, selectedColumns, true); query.append(" FROM \"").append(sourceTableName).append('"'); query.append(joinClauses); db.execSQL(query.toString()); } + private void appendColumns(StringBuilder query, String[] columns, boolean appendSourceTableName) { + boolean notFirst = false; + + for (String column : columns) { + if (notFirst) { + query.append(','); + } + + if (appendSourceTableName && column.indexOf('.') == NOT_FOUND) { + query.append('"').append(sourceTableName).append("\".\"").append(column).append('"'); + } else { + column = column.replace(".", "\".\""); + query.append('"').append(column).append('"'); + } + + notFirst = true; + } + } + public SqlInsertSelectBuilder join(String targetTable, String sourceColumn) { sourceColumn = sourceColumn.replace(".", "\".\""); joinClauses.append(" JOIN \"") // @@ -467,15 +503,12 @@ public SqlInsertBuilder integer(String column, Integer value) { return this; } - public SqlInsertBuilder bool(String column, Boolean value) { - contentValues.put(column, value); - return this; - } - public Long executeOn(SupportSQLiteDatabase db) { - //TODO Error handling in replacement of SQLiteDatabase#insertOrThrow, all-null-handling - // ... Handling of everything that was changed since the prior implementation - return db.insert(this.table, SQLiteDatabase.CONFLICT_FAIL, contentValues); + //In contrast to "SupportSQLiteDatabase#update" "insert" doesn't define how the contents of "contentValues" are bound. + //As opposed to the other methods in this class, we do actually pass "Integers" and "Strings" here and again they appear + //to end up in the "Array" in "SQLiteProgram". Currently there is no issue, + //but this has to be kept in mind if we ever change this method. See: "SqlUpdateBuilder#executeOn" + return db.insert(this.table, SQLiteDatabase.CONFLICT_NONE, contentValues); } } @@ -499,29 +532,9 @@ public SqlDeleteBuilder where(String column, Criterion criterion) { } public void executeOn(SupportSQLiteDatabase db) { + //"SupportSQLiteDatabase#delete" always binds the contents of "whereArgs" as "Strings". + //As of now we always pass an "Array", but this has to be kept in mind if we ever change this. See: "SqlUpdateBuilder#executeOn" db.delete(tableName, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); } } - - private static final int NOT_FOUND = -1; - - private static void appendColumns(StringBuilder query, String[] columns, String sourceTableName, boolean appendSourceTableName) { - boolean notFirst = false; - - for (String column : columns) { - if (notFirst) { - query.append(','); - } - - if (appendSourceTableName && column.indexOf('.') == NOT_FOUND) { - query.append('"').append(sourceTableName).append("\".\"").append(column).append('"'); - } else { - column = column.replace(".", "\".\""); - query.append('"').append(column).append('"'); - } - - notFirst = true; - } - } - } From 6d18c569fe172e119dd9af088a40ee6089cfa027 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:49:43 +0100 Subject: [PATCH 034/120] Performed minor cleanup Removed unnecessary comment/constructor Corrected error message Replaced unreachable log message with sanity check --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 2 +- data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 3 ++- .../main/java/org/cryptomator/data/db/entities/VaultEntity.kt | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 65771e22b..e2f470018 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -65,7 +65,7 @@ class UpgradeDatabaseTest { templateDbFile.copyTo(context.getDatabasePath(TEST_DB)) db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { override fun onCreate(db: SupportSQLiteDatabase) { - fail("Database should not be created, but copied from asset") + fail("Database should not be created, but copied from template") } override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index c4835e1f6..fce482f57 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -159,7 +159,8 @@ class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWra object DatabaseCallback : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { - LOG.i("Created database (v%s)", db.version) + //This should not be called + throw UnsupportedOperationException("Creation is handled as upgrade") } override fun onOpen(db: SupportSQLiteDatabase) { diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index 50b32e021..f7332d483 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -10,9 +10,9 @@ import androidx.room.PrimaryKey indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["folderPath", "folderCloudId"], unique = true)], // foreignKeys = [ForeignKey(CloudEntity::class, ["id"], ["folderCloudId"], onDelete = ForeignKey.RESTRICT)] ) -data class VaultEntity constructor( +data class VaultEntity( @PrimaryKey override val id: Long?, - val folderCloudId: Long?, //TODO Map to CloudEntity + val folderCloudId: Long?, val folderPath: String?, val folderName: String?, val cloudType: String, From af82df5017bcbf370e2244ecb8419a2d8a2e8c5e Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:47:50 +0100 Subject: [PATCH 035/120] Enabled foreign key constraints for migrations --- .../data/db/UpgradeDatabaseTest.kt | 9 ++- .../data/db/CryptomatorDatabase.kt | 11 ++- .../data/db/DatabaseAutoMigrationSpec.kt | 1 + .../cryptomator/data/db/DatabaseMigration.kt | 1 + .../org/cryptomator/data/db/DatabaseModule.kt | 11 ++- .../data/db/DatabaseOpenHelperFactory.kt | 76 +++++++++++++++++++ 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index e2f470018..55000f623 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -57,13 +57,20 @@ class UpgradeDatabaseTest { val helper: MigrationTestHelper = MigrationTestHelper( // instrumentation, // CryptomatorDatabase::class.java, // - listOf() //TODO AutoSpecs + listOf(), //TODO AutoSpecs + DatabaseOpenHelperFactory() ) @Before fun setup() { templateDbFile.copyTo(context.getDatabasePath(TEST_DB)) + + //This needs to stay in sync with changes to DatabaseOpenHelperFactory/PatchedCallback db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { + override fun onConfigure(db: SupportSQLiteDatabase) { + db.setForeignKeyConstraintsEnabled(true) + } + override fun onCreate(db: SupportSQLiteDatabase) { fail("Database should not be created, but copied from template") } diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 4f191a6fe..fa120f4fd 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -3,6 +3,7 @@ package org.cryptomator.data.db import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase +import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.entities.CloudEntity import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity @@ -23,4 +24,12 @@ abstract class CryptomatorDatabase : RoomDatabase() { abstract fun updateCheckDao(): UpdateCheckDao abstract fun vaultDao(): VaultDao -} \ No newline at end of file +} + +val SupportSQLiteDatabase.foreignKeyConstraintsEnabled: Boolean + get() { + query("PRAGMA foreign_keys;").use { cursor -> + check(cursor.count == 1 && cursor.moveToNext()) { "\"PRAGMA foreign_keys\" returned invalid value" } + return cursor.getLong(0) == 1L + } + } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt index 4d6d46a14..79de9fb4c 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseAutoMigrationSpec.kt @@ -9,6 +9,7 @@ abstract class DatabaseAutoMigrationSpec : AutoMigrationSpec { final override fun onPostMigrate(db: SupportSQLiteDatabase) { Timber.tag("DatabaseMigration").i("Ran automatic migration %s", javaClass.simpleName) + require(db.foreignKeyConstraintsEnabled) onPostMigrateInternal(db) } diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt index 182f221a6..59d248359 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseMigration.kt @@ -9,6 +9,7 @@ abstract class DatabaseMigration(startVersion: Int, endVersion: Int) : Migration final override fun migrate(database: SupportSQLiteDatabase) { Timber.tag("DatabaseMigration").i("Running %s (%d -> %d)", javaClass.simpleName, startVersion, endVersion) + require(database.foreignKeyConstraintsEnabled) migrateInternal(database) } diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index fce482f57..b4288ee8d 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -50,11 +50,15 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // + .openHelperFactory(DatabaseOpenHelperFactory()) // .build() //Fails if no migration is found (especially when downgrading) .also { // //Migrations are only triggered once the database is used for the first time. //-- Let's do that now and verify all went well before returning the database. - require(it.openHelper.writableDatabase.version == CRYPTOMATOR_DATABASE_VERSION) + it.openHelper.writableDatabase.run { + require(this.version == CRYPTOMATOR_DATABASE_VERSION) + require(this.foreignKeyConstraintsEnabled) + } LOG.i("Database built successfully") } } @@ -76,6 +80,11 @@ class DatabaseModule { val db = SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onConfigure(db: SupportSQLiteDatabase) { + db.disableWriteAheadLogging() + db.setForeignKeyConstraintsEnabled(true) + } + override fun onCreate(db: SupportSQLiteDatabase) { Upgrade0To1().migrate(db) } diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt new file mode 100644 index 000000000..11d11b68b --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -0,0 +1,76 @@ +package org.cryptomator.data.db + +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.util.named +import timber.log.Timber + +private val LOG = Timber.Forest.named("DatabaseOpenHelperFactory") + +//This needs to stay in sync with UpgradeDatabaseTest#setup +internal class DatabaseOpenHelperFactory( + private val delegate: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory() +) : SupportSQLiteOpenHelper.Factory { + + override fun create(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper { + LOG.d("Creating SupportSQLiteOpenHelper for database \"${configuration.name}\"") + return delegate.create(patchConfiguration(configuration)) + } +} + +private fun patchConfiguration(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper.Configuration { + return SupportSQLiteOpenHelper.Configuration( + context = configuration.context, + name = configuration.name, + callback = PatchedCallback(configuration.callback), + useNoBackupDirectory = configuration.useNoBackupDirectory, + allowDataLossOnRecovery = configuration.allowDataLossOnRecovery + ) +} + +private class PatchedCallback( + private val delegateCallback: SupportSQLiteOpenHelper.Callback, +) : SupportSQLiteOpenHelper.Callback(delegateCallback.version) { + + override fun onConfigure(db: SupportSQLiteDatabase) { + LOG.d("Called onConfigure for \"${db.path}\"@${db.version}") + db.setForeignKeyConstraintsEnabled(true) + // + delegateCallback.onConfigure(db) + // + } + + override fun onCreate(db: SupportSQLiteDatabase) { + LOG.e(Exception(), "Called onCreate for \"${db.path}\"@${db.version}") + // + delegateCallback.onCreate(db) + // + } + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + LOG.i("Called onUpgrade for \"${db.path}\"@${db.version} ($oldVersion -> $newVersion)") + // + delegateCallback.onUpgrade(db, oldVersion, newVersion) + // + } + + override fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + LOG.e(Exception(), "Called onDowngrade for \"${db.path}\"@${db.version} ($oldVersion -> $newVersion)") + // + delegateCallback.onDowngrade(db, oldVersion, newVersion) + // + } + + override fun onCorruption(db: SupportSQLiteDatabase) { + // + delegateCallback.onCorruption(db) + // + } + + override fun onOpen(db: SupportSQLiteDatabase) { + // + delegateCallback.onOpen(db) + // + } +} \ No newline at end of file From 465c1967a0ee72a0d1cb9a9b8637972d82f33430 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:25:07 +0100 Subject: [PATCH 036/120] Fixed wrong test --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 55000f623..1b5768d66 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -547,7 +547,7 @@ class UpgradeDatabaseTest { Sql.query("VAULT_ENTITY").executeOn(db).use { it.moveToFirst() - Assert.assertThat(it.getString(it.getColumnIndex("FOLDER_CLOUD_ID")), CoreMatchers.`is`("3")) + Assert.assertThat(it.getString(it.getColumnIndex("FOLDER_CLOUD_ID")), CoreMatchers.nullValue()) Assert.assertThat(it.getString(it.getColumnIndex("FOLDER_PATH")), CoreMatchers.`is`("path")) Assert.assertThat(it.getString(it.getColumnIndex("FOLDER_NAME")), CoreMatchers.`is`("name")) Assert.assertThat(it.getString(it.getColumnIndex("CLOUD_TYPE")), CoreMatchers.`is`(CloudType.ONEDRIVE.name)) From 0cc2ea01689aa79568cc8d13311b89e3c464d878 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:12:26 +0100 Subject: [PATCH 037/120] Updated "Sql.java" to offer new id column --- .../data/db/UpgradeDatabaseTest.kt | 2 +- .../cryptomator/data/db/migrations/Sql.java | 29 +++++++++++++++++-- .../data/db/migrations/legacy/Upgrade0To1.kt | 6 ++-- .../db/migrations/legacy/Upgrade10To11.kt | 6 ++-- .../data/db/migrations/legacy/Upgrade1To2.kt | 2 +- .../data/db/migrations/legacy/Upgrade3To4.kt | 6 ++-- .../data/db/migrations/legacy/Upgrade4To5.kt | 8 ++--- .../data/db/migrations/legacy/Upgrade5To6.kt | 8 ++--- .../data/db/migrations/legacy/Upgrade6To7.kt | 2 +- 9 files changed, 47 insertions(+), 22 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 1b5768d66..47497f1da 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -368,7 +368,7 @@ class UpgradeDatabaseTest { Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) Sql.createTable("UPDATE_CHECK_ENTITY") // - .id() // + .pre14Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 2857bcc76..62f6196d5 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -332,6 +332,14 @@ private void appendColumns(StringBuilder query, String[] columns, boolean append } public SqlInsertSelectBuilder join(String targetTable, String sourceColumn) { + return join(targetTable, "id", sourceColumn); + } + + public SqlInsertSelectBuilder pre14Join(String targetTable, String sourceColumn) { + return join(targetTable, "_id", sourceColumn); + } + + public SqlInsertSelectBuilder join(String targetTable, String targetColumn, String sourceColumn) { sourceColumn = sourceColumn.replace(".", "\".\""); joinClauses.append(" JOIN \"") // .append(targetTable) // @@ -339,7 +347,9 @@ public SqlInsertSelectBuilder join(String targetTable, String sourceColumn) { .append(sourceColumn) // .append("\" = \"") // .append(targetTable) // - .append("\".\"_id\" "); + .append("\".\"") // + .append(targetColumn) // + .append("\" "); return this; } @@ -368,6 +378,11 @@ private SqlCreateTableBuilder(String table) { } public SqlCreateTableBuilder id() { + column("id", INTEGER, PRIMARY_KEY); + return this; + } + + public SqlCreateTableBuilder pre14Id() { column("_id", INTEGER, PRIMARY_KEY); return this; } @@ -418,6 +433,14 @@ public void executeOn(SupportSQLiteDatabase db) { } public SqlCreateTableBuilder foreignKey(String column, String targetTable, ForeignKeyBehaviour... behaviours) { + return foreignKey(column, targetTable, "id", behaviours); + } + + public SqlCreateTableBuilder pre14ForeignKey(String column, String targetTable, ForeignKeyBehaviour... behaviours) { + return foreignKey(column, targetTable, "_id", behaviours); + } + + public SqlCreateTableBuilder foreignKey(String column, String targetTable, String targetColumn, ForeignKeyBehaviour... behaviours) { foreignKeys // .append(", CONSTRAINT FK_") // .append(column) // @@ -427,7 +450,9 @@ public SqlCreateTableBuilder foreignKey(String column, String targetTable, Forei .append(column) // .append(") REFERENCES ") // .append(targetTable) // - .append("(_id)"); + .append("(") // + .append(targetColumn) // + .append(")"); for (ForeignKeyBehaviour behaviour : behaviours) { foreignKeys.append(" ").append(behaviour.getText()); diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt index bd61f883d..90555f214 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt @@ -22,7 +22,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseMigration(0, 1) { private fun createCloudEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("CLOUD_ENTITY") // - .id() // + .pre14Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("WEBDAV_URL") // @@ -33,13 +33,13 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseMigration(0, 1) { private fun createVaultEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("VAULT_ENTITY") // - .id() // + .pre14Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.createUniqueIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") // .on("VAULT_ENTITY") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt index 29acffd05..87e8345df 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt @@ -30,7 +30,7 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseMigration(10, 11) { private fun addFormatAndShorteningToDbEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .id() // + .pre14Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // @@ -39,14 +39,14 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseMigration(10, 11) { .optionalInt("POSITION") // .optionalInt("FORMAT") // .optionalInt("SHORTENING_THRESHOLD") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt index b4eb5a2bf..7f0b08035 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt @@ -18,7 +18,7 @@ internal class Upgrade1To2 @Inject constructor() : DatabaseMigration(1, 2) { db.beginTransaction() try { Sql.createTable("UPDATE_CHECK_ENTITY") // - .id() // + .pre14Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt index 9dd3d0cc2..22571eba6 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt @@ -24,21 +24,21 @@ internal class Upgrade3To4 @Inject constructor() : DatabaseMigration(3, 4) { private fun addPositionToVaultSchema(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .id() // + .pre14Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt index e957f5f16..a1b54bb8c 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt @@ -23,7 +23,7 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // - .id() // + .pre14Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("URL") // @@ -45,21 +45,21 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .id() // + .pre14Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt index b038da90a..857f6fc82 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt @@ -23,7 +23,7 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // - .id() // + .pre14Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("URL") // @@ -48,21 +48,21 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .id() // + .pre14Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt index a89e5e6b2..6029d1981 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt @@ -25,7 +25,7 @@ internal class Upgrade6To7 @Inject constructor() : DatabaseMigration(6, 7) { Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) Sql.createTable("UPDATE_CHECK_ENTITY") // - .id() // + .pre14Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // From 9718e51283a02118a8fbbcc727ce8360950054d7 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:19:04 +0100 Subject: [PATCH 038/120] Added additional cleanup to UpgradeDatabaseTest --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 47497f1da..c8ffa78e1 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -63,7 +63,14 @@ class UpgradeDatabaseTest { @Before fun setup() { - templateDbFile.copyTo(context.getDatabasePath(TEST_DB)) + context.getDatabasePath(TEST_DB).also { dbFile -> + if (dbFile.exists()) { + //This may happen when killing the process while using the debugger + println("Test database \"${dbFile.absolutePath}\" not cleaned up. Deleting...") + dbFile.delete() + } + templateDbFile.copyTo(dbFile) + } //This needs to stay in sync with changes to DatabaseOpenHelperFactory/PatchedCallback db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { From 0290c20403bf4c2118df407e56488bd402f0f411 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:24:27 +0100 Subject: [PATCH 039/120] Cleaned up Entities and Entity mapping --- .../org/cryptomator/data/db/entities/CloudEntity.kt | 13 +++++++++---- .../data/db/entities/UpdateCheckEntity.kt | 3 +-- .../data/db/mappers/CloudEntityMapper.java | 7 +++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt index 33cad47ce..34dc89951 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt @@ -4,9 +4,7 @@ import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "CLOUD_ENTITY") -data class CloudEntity @JvmOverloads constructor( - //TODO Remove @JvmOverloads - //TODO Nullability +data class CloudEntity( @PrimaryKey override var id: Long?, var type: String, var accessToken: String? = null, @@ -16,4 +14,11 @@ data class CloudEntity @JvmOverloads constructor( var s3Bucket: String? = null, var s3Region: String? = null, var s3SecretKey: String? = null, -) : DatabaseEntity \ No newline at end of file +) : DatabaseEntity { + + companion object { + + @JvmStatic + fun newEntity(id: Long?, name: String): CloudEntity = CloudEntity(id, name) + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt index a69920fe3..056404388 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/UpdateCheckEntity.kt @@ -4,8 +4,7 @@ import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "UPDATE_CHECK_ENTITY") -data class UpdateCheckEntity @JvmOverloads constructor( - //TODO Remove @JvmOverloads +data class UpdateCheckEntity( @PrimaryKey override var id: Long?, var licenseToken: String?, var releaseNote: String?, diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index d0a26ac7f..584e5f314 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -11,6 +11,8 @@ import org.cryptomator.domain.S3Cloud; import org.cryptomator.domain.WebDavCloud; +import java.util.Objects; + import javax.inject.Inject; import javax.inject.Singleton; @@ -87,8 +89,9 @@ public Cloud fromEntity(CloudEntity entity) { @Override public CloudEntity toEntity(Cloud domainObject) { - CloudEntity result = new CloudEntity(domainObject.id(), domainObject.type().name()); - switch (domainObject.type()) { + CloudType type = Objects.requireNonNull(domainObject.type()); + CloudEntity result = CloudEntity.newEntity(domainObject.id(), type.name()); + switch (type) { case DROPBOX: result.setAccessToken(((DropboxCloud) domainObject).accessToken()); result.setUsername(((DropboxCloud) domainObject).username()); From 4f2d05f4059242149bdddd0498df6a9c0c523b22 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:02:01 +0100 Subject: [PATCH 040/120] Added explicit orders to VaultEntity at v13 The file was generated by applying the following patch onto https://github.com/cryptomator/android/blob/d9611360b20b2aa722bae802c504d716f4021e13/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt ```diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index e99b4ea..202f20e 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -4,11 +4,12 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index +import androidx.room.Index.Order import androidx.room.PrimaryKey @Entity( tableName = "VAULT_ENTITY", // - indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["FOLDER_PATH", "FOLDER_CLOUD_ID"], unique = true)], // + indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["FOLDER_PATH", "FOLDER_CLOUD_ID"], orders = [Order.ASC, Order.ASC], unique = true)], // foreignKeys = [ForeignKey(CloudEntity::class, ["_id"], ["FOLDER_CLOUD_ID"], onDelete = ForeignKey.SET_NULL)] ) data class VaultEntity constructor( ``` --- .../13.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json index 00c61a7a8..ff8bfad70 100644 --- a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 13, - "identityHash": "ab8cd826759896443cfcac2f825a509c", + "identityHash": "932cb16d86be1f723e0e4b0ef2bc542a", "entities": [ { "tableName": "CLOUD_ENTITY", @@ -201,8 +201,11 @@ "FOLDER_PATH", "FOLDER_CLOUD_ID" ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`FOLDER_PATH`, `FOLDER_CLOUD_ID`)" + "orders": [ + "ASC", + "ASC" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`FOLDER_PATH` ASC, `FOLDER_CLOUD_ID` ASC)" } ], "foreignKeys": [ @@ -223,7 +226,7 @@ "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, 'ab8cd826759896443cfcac2f825a509c')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '932cb16d86be1f723e0e4b0ef2bc542a')" ] } } \ No newline at end of file From d15d3b2e8bcb2f41cb897823c7515d566a46a037 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:07:01 +0100 Subject: [PATCH 041/120] Added explicit orders to VaultEntity at current version --- .../14.json | 11 +++++++---- .../org/cryptomator/data/db/entities/VaultEntity.kt | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json index 4ef814a19..78e185abe 100644 --- a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 14, - "identityHash": "5a294fd8ee96abf919ac6097dcab3306", + "identityHash": "09c519f6022b510f2ef05f52570806d9", "entities": [ { "tableName": "CLOUD_ENTITY", @@ -201,8 +201,11 @@ "folderPath", "folderCloudId" ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`folderPath`, `folderCloudId`)" + "orders": [ + "ASC", + "ASC" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`folderPath` ASC, `folderCloudId` ASC)" } ], "foreignKeys": [ @@ -223,7 +226,7 @@ "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, '5a294fd8ee96abf919ac6097dcab3306')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '09c519f6022b510f2ef05f52570806d9')" ] } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index f7332d483..3eb584bc9 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -3,11 +3,12 @@ package org.cryptomator.data.db.entities import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index +import androidx.room.Index.Order import androidx.room.PrimaryKey @Entity( tableName = "VAULT_ENTITY", // - indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["folderPath", "folderCloudId"], unique = true)], // + indices = [Index(name = "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", value = ["folderPath", "folderCloudId"], orders = [Order.ASC, Order.ASC], unique = true)], // foreignKeys = [ForeignKey(CloudEntity::class, ["id"], ["folderCloudId"], onDelete = ForeignKey.RESTRICT)] ) data class VaultEntity( From 5035ce4da968920d28056b81cfd3068b5046fc07 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:40:04 +0100 Subject: [PATCH 042/120] Added tests to handle mismatch between db and schema --- .../data/db/UpgradeDatabaseTest.kt | 96 +++++++++++++++++++ .../cryptomator/data/db/migrations/Sql.java | 7 ++ .../db/migrations/manual/Migration12To13.kt | 18 ++++ 3 files changed, 121 insertions(+) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index c8ffa78e1..6ba687d4c 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -29,6 +29,8 @@ import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Assert import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before import org.junit.Rule @@ -686,4 +688,98 @@ class UpgradeDatabaseTest { Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent())) } + + @Test + fun migrate12To14ForeignKeySideEffects() { //See: Migration12To13 + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) + + val pre13Statement = referencesStatement(db) + val pre13Expected = "CONSTRAINT FK_FOLDER_CLOUD_ID_CLOUD_ENTITY FOREIGN KEY (FOLDER_CLOUD_ID) REFERENCES CLOUD_ENTITY(_id) ON DELETE SET NULL" + //This is a sanity check and may need to be updated if Sql.java is changed + assertTrue("Expected \".*$pre13Expected.*\", got \"$pre13Statement\"", pre13Statement.contains(pre13Expected)) + db.close() + + helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + val statement = referencesStatement(migratedDb) + assertEquals(pre13Statement, statement) + } + + helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + val statement = referencesStatement(migratedDb) + val expected = "FOREIGN KEY(folderCloudId) REFERENCES CLOUD_ENTITY(id) ON" + assertTrue("Expected \".*$expected.*\", got \"$statement\"", statement.contains(expected)) + assertFalse(statement.contains("CONSTRAINT")) + assertFalse(statement.contains("FK_FOLDER_CLOUD_ID_CLOUD_ENTITY")) + + assertTrue(statement.contains("ON UPDATE NO ACTION")) + assertTrue(statement.contains("ON DELETE RESTRICT")) + } + } + + private fun referencesStatement(db: SupportSQLiteDatabase): String { + return Sql.SqlQueryBuilder("sqlite_master") // + .columns(listOf("sql")) // + .where("tbl_name", Sql.eq("VAULT_ENTITY")) // + .where("sql", Sql.like("%REFERENCES%")) // + .executeOn(db).use { + assertEquals(it.count, 1) + assertTrue(it.moveToNext()) + it.getString(0) + }.filterNot { it in "\"'`" } + } + + @Test + fun migrate12To14IndexSideEffects() { //See: Migration12To13 + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) + + val pre13Statement = indexStatement(db) + val pre13Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC)" + //This is a sanity check and may need to be updated if Sql.java is changed + assertEquals(pre13Expected, pre13Statement) + db.close() + + helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + val statement = indexStatement(migratedDb) + assertEquals(pre13Statement, statement) + } + + helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + val statement = indexStatement(migratedDb) + val expected = "CREATE UNIQUE INDEX `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `VAULT_ENTITY` (`folderPath` ASC, `folderCloudId` ASC)" + assertEquals(expected, statement) + } + } + + private fun indexStatement(db: SupportSQLiteDatabase): String { + return Sql.SqlQueryBuilder("sqlite_master") // + .columns(listOf("sql")) // + .where("type", Sql.eq("index")) + .where("name", Sql.eq("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID")) + .where("tbl_name", Sql.eq("VAULT_ENTITY")) // + .executeOn(db).use { + assertEquals(it.count, 1) + assertTrue(it.moveToNext()) + it.getString(0) + } + } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 62f6196d5..24085cd33 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -78,6 +78,13 @@ public static Criterion eq(final Long value) { return (column, whereClause, whereArgs) -> whereClause.append('"').append(column).append("\" = ").append(value); } + public static Criterion like(final String value) { + return (column, whereClause, whereArgs) -> { + whereClause.append('"').append(column).append("\" LIKE(?)"); + whereArgs.add(value); + }; + } + public static ValueHolder toString(final String value) { return (column, contentValues) -> contentValues.put(column, value); } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt index a2b1fb9e3..ba0fcc38c 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt @@ -8,6 +8,24 @@ import javax.inject.Singleton @Singleton internal class Migration12To13 @Inject constructor() : DatabaseMigration(12, 13) { + //After migrating from v12 to v13 the database differs as follows: + //1a) The `room_master_table` exists and contains id data + + //A v13 database created by room differs from an migrated v13 database as follows: + //1b) The `room_master_table` exists and contains id data + //2) The foreign key "VAULT_ENTITY.FOLDER_CLOUD_ID -> CLOUD_ENTITY._id" is unnamed + //3) The index "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID" is formatted differently internally + //4) The database does not contain actual data + //-- This does never happen in practice as databases are created by upgrading, but illustrates the slightly different schemas. + //1a,b and 4 are intended behavior, but 2 and 3 cause the schema and the actual database to slightly drift out of sync. + //This *should* not cause any problems. + //The migration to v14 then recreates the tables and therefore resolves 2 and 3 as a side effect. Once the migration to + //v14 has finished, the schema and the database are back in sync. + + //Since this is a bit hacky, "UpgradeDatabaseTest" contains the "migrate12To14IndexSideEffects" + //and "migrate12To14ForeignKeySideEffects" methods to bring attention to any potential future changes + //that change this behavior. + override fun migrateInternal(db: SupportSQLiteDatabase) { //NO-OP } From ee4a18cfe6db1aaef67caba3196bebc8e016cfd7 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:54:32 +0200 Subject: [PATCH 043/120] Removed erroneous import for "okio.use" in favor of "kotlin.io.use" Introduced by bfbff577dd629519263998792a5b0f4cce792e26. --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 6ba687d4c..90600434b 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -36,7 +36,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import okio.use private const val TEST_DB = "migration-test" private const val LATEST_LEGACY_MIGRATION = 12 From d7e6be610645bd2acfbe13e7e58dbfe0064535f2 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:04:33 +0200 Subject: [PATCH 044/120] Added utility methods for comparing databases --- .../data/db/CryptomatorDatabase.kt | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index fa120f4fd..f73d47f0a 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -1,5 +1,6 @@ package org.cryptomator.data.db +import android.database.Cursor import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase @@ -7,6 +8,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase import org.cryptomator.data.db.entities.CloudEntity import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity +import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 const val DATABASE_NAME = "Cryptomator" @@ -32,4 +34,76 @@ val SupportSQLiteDatabase.foreignKeyConstraintsEnabled: Boolean check(cursor.count == 1 && cursor.moveToNext()) { "\"PRAGMA foreign_keys\" returned invalid value" } return cursor.getLong(0) == 1L } - } \ No newline at end of file + } +val SupportSQLiteDatabase.hasRoomMasterTable: Boolean + get() { + return Sql.query("sqlite_master") // + .columns(listOf("count(*)")) // + .where("name", Sql.eq("room_master_table")) // + .executeOn(this) // + .use { cursor -> + cursor.moveToNext() && cursor.getInt(0) == 1 + } + } + +/** + * @param type The type of the value; any of [Cursor.FIELD_TYPE_NULL], [Cursor.FIELD_TYPE_INTEGER], [Cursor.FIELD_TYPE_FLOAT], [Cursor.FIELD_TYPE_STRING] or [Cursor.FIELD_TYPE_BLOB] + * @param value The value matching the type; `null` for [Cursor.FIELD_TYPE_NULL] + */ +data class CursorValue(val type: Int, val value: Any?) { + + init { + require((type == Cursor.FIELD_TYPE_NULL) == (value == null)) + } +} + +fun Cursor.getValue(columnIndex: Int): CursorValue { + require(0 <= columnIndex && columnIndex < this.columnCount) { "Column index $columnIndex outside range 0 <= index < ${this.columnCount}." } + return when (getType(columnIndex)) { + Cursor.FIELD_TYPE_INTEGER -> CursorValue(Cursor.FIELD_TYPE_INTEGER, getLong(columnIndex)) + Cursor.FIELD_TYPE_FLOAT -> CursorValue(Cursor.FIELD_TYPE_FLOAT, getDouble(columnIndex)) + Cursor.FIELD_TYPE_STRING -> CursorValue(Cursor.FIELD_TYPE_STRING, getString(columnIndex)) + Cursor.FIELD_TYPE_BLOB -> CursorValue(Cursor.FIELD_TYPE_BLOB, getBlob(columnIndex)) + Cursor.FIELD_TYPE_NULL -> CursorValue(Cursor.FIELD_TYPE_NULL, null) + else -> throw IllegalArgumentException() + } +} + +fun Cursor.equalsCursor(other: Cursor): Boolean { + if (this.count != other.count) { + return false + } + if (this.columnCount != other.columnCount) { + return false + } + if (this.columnNames.uniqueToSet() != other.columnNames.uniqueToSet()) { + return false + } + + val thisPos = this.position + this.moveToPosition(-1) + + val otherPos = other.position + other.moveToPosition(-1) + + while (this.moveToNext()) { + require(other.moveToNext()) + for (name in this.columnNames) { + val valueThis = this.getValue(this.getColumnIndexOrThrow(name)) + val valueOther = other.getValue(other.getColumnIndexOrThrow(name)) + + if (valueThis != valueOther) { + this.moveToPosition(thisPos) + other.moveToPosition(otherPos) + return false + } + } + } + this.moveToPosition(thisPos) + other.moveToPosition(otherPos) + return true +} + +private fun Array.uniqueToSet(): Set = toSet().also { + require(this.size == it.size) { "Array contained ${this.size - it.size} duplicate elements." } +} \ No newline at end of file From aed7e38435a0fa2b59e54889cab49191fe0d503f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:05:53 +0200 Subject: [PATCH 045/120] Added "assertCursorEquals" --- .../data/db/CryptomatorAssert.java | 43 ++++++++++++++ .../data/db/CryptomatorDatabase.kt | 57 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java new file mode 100644 index 000000000..2e6842883 --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java @@ -0,0 +1,43 @@ +package org.cryptomator.data.db; + +import android.database.Cursor; + +import com.google.android.gms.common.util.Strings; + +import java.text.MessageFormat; + +import static org.junit.Assert.fail; + +public class CryptomatorAssert { + + private CryptomatorAssert() { + } + + public static void assertCursorEquals(Cursor expected, Cursor actual) { + assertCursorEquals(null, expected, actual); + } + + public static void assertCursorEquals(String message, Cursor expected, Cursor actual) { + if ((expected == null) != (actual == null)) { + failCursorNotEquals(message, expected, actual); + return; + } + if (expected == null || CryptomatorDatabaseKt.equalsCursor(expected, actual)) { + return; + } + + failCursorNotEquals(message, expected, actual); + } + + private static void failCursorNotEquals(String message, Cursor expected, Cursor actual) { + String failMessage = MessageFormat.format("{0}\n" + // + "---------- expected ----------\n" + // + "{1}\n" + // + "---------- but was ----------\n" + // + "{2}", // + message != null && !Strings.isEmptyOrWhitespace(message) ? message : "", // + CryptomatorDatabaseKt.stringify(expected), // + CryptomatorDatabaseKt.stringify(actual)); + fail(failMessage); + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index f73d47f0a..c16d70526 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -10,6 +10,7 @@ import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 +import kotlin.math.max const val DATABASE_NAME = "Cryptomator" const val CRYPTOMATOR_DATABASE_VERSION = 14 @@ -69,6 +70,20 @@ fun Cursor.getValue(columnIndex: Int): CursorValue { } } +fun Cursor.getValueAsString(columnIndex: Int): String { + require(0 <= columnIndex && columnIndex < this.columnCount) { "Column index $columnIndex outside range 0 <= index < ${this.columnCount}." } + return when (getType(columnIndex)) { + Cursor.FIELD_TYPE_INTEGER -> getLong(columnIndex).toString() + Cursor.FIELD_TYPE_FLOAT -> getDouble(columnIndex).toBigDecimal().toPlainString() + Cursor.FIELD_TYPE_STRING -> "\"${getString(columnIndex)}\"" + Cursor.FIELD_TYPE_BLOB -> getBlob(columnIndex).asSequence().map { + it.toUByte().toString(16) + }.joinToString(separator = " ", prefix = "0x") + Cursor.FIELD_TYPE_NULL -> "null" + else -> throw IllegalArgumentException() + } +} + fun Cursor.equalsCursor(other: Cursor): Boolean { if (this.count != other.count) { return false @@ -104,6 +119,48 @@ fun Cursor.equalsCursor(other: Cursor): Boolean { return true } +fun Cursor?.stringify(): String { + if (this == null) { + return "" + } + if (this.columnCount == 0) { + return "" + } + if (this.count == 0) { + return this.columnNames.joinToString(" ") + } + val startPos = this.position + this.moveToPosition(-1) + + val columnWidths: MutableMap = this.columnNames.associateWithTo(mutableMapOf()) { it.length } + val values = buildList(this.count * this.columnCount) { + while (moveToNext()) { + for (name in columnNames) { + val value = getValueAsString(getColumnIndexOrThrow(name)) + columnWidths.compute(name) { _, currentWidth: Int? -> max(currentWidth!!, value.length) } + add(value) + } + } + } + + this.moveToPosition(startPos) + return buildString(((this.count + 1) * (columnWidths.values.sum() + this.columnCount))) { + appendLine(columnNames.asSequence().map { it.padEnd(columnWidths[it]!!) }.joinToString(" ")) + appendLine() + values.forEachIndexed { i: Int, value: String -> + append(value.padEnd(columnWidths[columnNames[i % columnCount]]!!)) + if ((i == values.size - 1)) { + return@buildString + } + if ((i + 1) % columnCount == 0) { + appendLine() + } else { + append(" ") + } + } + } +} + private fun Array.uniqueToSet(): Set = toSet().also { require(this.size == it.size) { "Array contained ${this.size - it.size} duplicate elements." } } \ No newline at end of file From a6d477643bb7dc57f2812c55340277257f84917b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 5 Mar 2024 00:35:12 +0100 Subject: [PATCH 046/120] Commented "Cursor?.stringify" defined in CryptomatorDatabase.kt --- .../java/org/cryptomator/data/db/CryptomatorDatabase.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index c16d70526..1b0912c95 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -144,15 +144,20 @@ fun Cursor?.stringify(): String { } this.moveToPosition(startPos) - return buildString(((this.count + 1) * (columnWidths.values.sum() + this.columnCount))) { + val stringifiedRowCount = this.count + 1 /* Header */ + val rowCapacity = columnWidths.values.sum() + this.columnCount /* V-Spaces */ // - 1 (V-Spaces) + 1 (Line breaks) + val capacity = stringifiedRowCount * rowCapacity // + 1 (H-Space) - 1 (No Line break in last line) + return buildString(capacity) { appendLine(columnNames.asSequence().map { it.padEnd(columnWidths[it]!!) }.joinToString(" ")) appendLine() values.forEachIndexed { i: Int, value: String -> append(value.padEnd(columnWidths[columnNames[i % columnCount]]!!)) if ((i == values.size - 1)) { + //Last element return@buildString } if ((i + 1) % columnCount == 0) { + //Last element in line appendLine() } else { append(" ") From c13b12ffc8d441f344793f2436506974b145899f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:17:21 +0200 Subject: [PATCH 047/120] Added tests for migration to v13 UpgradeDatabaseTest.kt:816 and UpgradeDatabaseTest.kt:895 caused the tests to fail because of issue #529 [1]. Consequently they have been commented out until the issue is resolved. [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/UpgradeDatabaseTest.kt | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 90600434b..a49364d2e 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -1,7 +1,9 @@ package org.cryptomator.data.db import android.content.Context +import android.database.Cursor import androidx.room.testing.MigrationTestHelper +import androidx.room.util.copyAndClose import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory @@ -9,6 +11,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.google.common.base.Optional +import org.cryptomator.data.db.CryptomatorAssert.assertCursorEquals import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -781,4 +784,142 @@ class UpgradeDatabaseTest { it.getString(0) } } + + @Test + fun migrate12To13() { + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) + + assertEquals(12, db.version) + val pre13Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> + val cursor = Sql.query(tableName).executeOn(db) + copyAndClose(cursor) + } + db.close() + + helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + assertTrue(migratedDb.hasRoomMasterTable) + assertEquals(13, migratedDb.version) + + for (preTable in pre13Tables) { + preTable.value.use { preCursor -> + Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> + //assertCursorEquals(preCursor, postCursor) + } + } + } + } + } + + @Test + fun migrate12To13WithData() { + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) + + Sql.insertInto("CLOUD_ENTITY") // + .integer("_id", 3) // + .text("TYPE", CloudType.LOCAL.name) // + .text("URL", "url1") // + .text("USERNAME", "username1") // + .text("WEBDAV_CERTIFICATE", "certificate1") // + .text("ACCESS_TOKEN", "accessToken1") // + .text("S3_BUCKET", "s3Bucket1") // + .text("S3_REGION", "s3Region1") // + .text("S3_SECRET_KEY", "s3SecretKey1") // + .executeOn(db) + + Sql.insertInto("VAULT_ENTITY") // + .integer("_id", 10) // + .integer("FOLDER_CLOUD_ID", 1) // + .text("FOLDER_PATH", "path1") // + .text("FOLDER_NAME", "name1") // + .text("CLOUD_TYPE", CloudType.DROPBOX.name) // + .text("PASSWORD", "password1") // + .integer("POSITION", 10) // + .integer("FORMAT", 42) // + .integer("SHORTENING_THRESHOLD", 110) // + .executeOn(db) + + Sql.insertInto("VAULT_ENTITY") // + .integer("_id", 20) // + .integer("FOLDER_CLOUD_ID", 3) // + .text("FOLDER_PATH", "path2") // + .text("FOLDER_NAME", "name2") // + .text("CLOUD_TYPE", CloudType.LOCAL.name) // + .text("PASSWORD", "password2") // + .integer("POSITION", 20) // + .integer("FORMAT", 43) // + .integer("SHORTENING_THRESHOLD", 120) // + .executeOn(db) + + Sql.update("UPDATE_CHECK_ENTITY") // + .set("LICENSE_TOKEN", Sql.toString("license1")) // + .set("RELEASE_NOTE", Sql.toString("note1")) // + .set("VERSION", Sql.toString("version1")) // + .set("URL_TO_APK", Sql.toString("urlToApk1")) // + .set("APK_SHA256", Sql.toString("sha1")) // + .set("URL_TO_RELEASE_NOTE", Sql.toString("urlToNote1")) // + .executeOn(db) + + assertEquals(12, db.version) + val pre13Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> + copyAndClose(Sql.query(tableName).executeOn(db)) + } + db.close() + + helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + assertTrue(migratedDb.hasRoomMasterTable) + assertEquals(13, migratedDb.version) + + for (preTable in pre13Tables) { + preTable.value.use { preCursor -> + Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> + //assertCursorEquals(preCursor, postCursor) + } + } + } + } + } + + //TODO Test metadata of non-entity tables for v13, v14 + //TODO Test metadata and content of entity tables for v14 + + @Test + fun migrate1To13WithRoom() { + db.version = 1 + db.close() + helper.runMigrationsAndValidate( + TEST_DB, 13, true, + Upgrade1To2(), + Upgrade2To3(context), + Upgrade3To4(), + Upgrade4To5(), + Upgrade5To6(), + Upgrade6To7(), + Upgrade7To8(), + Upgrade8To9(sharedPreferencesHandler), + Upgrade9To10(sharedPreferencesHandler), + Upgrade10To11(), + Upgrade11To12(sharedPreferencesHandler), + Migration12To13() + ) + } } \ No newline at end of file From 267b20b7e109253d718496269b723393c960d564 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:21:16 +0200 Subject: [PATCH 048/120] Added tests/snippets to explain/reproduce bug #529 [1] Copied from the Android-Issue-153521693 project [2] *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 [2] https://github.com/SailReal/Android-Issue-153521693/tree/cfb5033cf287572ac4b4972700f1656ce2b61d03/src/androidTest/java/de/skymatic/android_issue153521693 --- .../android_issue153521693/BugTest.kt | 246 ++++++++++++++++++ .../android_issue153521693/SmallBugTest.kt | 136 ++++++++++ 2 files changed, 382 insertions(+) create mode 100644 data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt create mode 100644 data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt new file mode 100644 index 000000000..3d7f4caa1 --- /dev/null +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt @@ -0,0 +1,246 @@ +/* + +MIT License + +Copyright (c) 2024 Skymatic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +package de.skymatic.android_issue153521693 + +import android.content.ContentValues +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase.* +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BugTest { + + private val TEST_DB = "bug-test" + + private val context = InstrumentationRegistry.getInstrumentation().context + + private lateinit var db: SupportSQLiteDatabase + + @Before + fun setup() { + context.getDatabasePath(TEST_DB).delete() //Clean up last test + + //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions + /* Database is created */ + SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() + + /* Database is closed, e.g. the app has been closed */ + + //////////////////////////////////////////// + + /* Database is opened, not created */ + + db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + } + + @After + fun tearDown() { + if (this::db.isInitialized) { + db.close() + } + } + + private fun createDb(db: SupportSQLiteDatabase) { + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") + + db.version = 1 + } + + private fun modifySchema(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL," + + "`column2` TEXT" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") + db.execSQL("DROP TABLE `TEST_TABLE_OLD`") + db.update( + "TEST_TABLE", + CONFLICT_NONE, + ContentValues().also { it.put("column2", "content2"); }, + null, + null + ) + } + + private fun assertBroken(cursor: Cursor) { + //cursor.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertEquals(2, cursor.columnCount) //Should be 3! + assertArrayEquals( + arrayOf("column0", "column1"), + cursor.columnNames + ) //Should be ["column0", "column1", "column2"] + assertEquals(-1, cursor.getColumnIndex("column2")) //Should be 2 + } + + private fun assertNotBroken(cursor: Cursor) { + //cursor.moveToFirst() //Not needed and it wouldn't fix this cursor either way. + assertEquals(3, cursor.columnCount) + assertArrayEquals(arrayOf("column0", "column1", "column2"), cursor.columnNames) + assertEquals(2, cursor.getColumnIndex("column2")) + } + + @Test + fun causeBugWithoutBackticks() { + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + assertBroken(it) + } + } + + @Test + fun causeBugWithBackticks() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + } + + @Test + fun causeBugWithMove1() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + it.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertBroken(it) + } + + db.query("SELECT * FROM `TEST_TABLE`").use { + //it.moveToFirst() //Not needed and it wouldn't fix this cursor either way. + assertNotBroken(it) + } + } + + @Test + fun causeBugWithMove2() { + db.query("SELECT * FROM TEST_TABLE").close() + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + + db.query("SELECT * FROM TEST_TABLE").use { + it.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertBroken(it) + } + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + } + + @Test + fun doNotCauseBugWithMixedBackticks1() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + assertNotBroken(it) + } + } + + @Test + fun doNotCauseBugWithMixedBackticks2() { + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } + + @Test + fun doNotCauseBugWithoutFirstCall() { + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } + + @Test + fun doNotCauseBugWithRefresh() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + it.count //Alternatively: it.moveToNext(), it.moveToFirst() + } + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } +} \ No newline at end of file diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt new file mode 100644 index 000000000..2a467326e --- /dev/null +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt @@ -0,0 +1,136 @@ +/* + +MIT License + +Copyright (c) 2024 Skymatic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +package de.skymatic.android_issue153521693 + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase.* +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SmallBugTest { + + private val TEST_DB = "small-bug-test" + + private val context = InstrumentationRegistry.getInstrumentation().context + + private lateinit var db: SupportSQLiteDatabase + + @Before + fun setup() { + context.getDatabasePath(TEST_DB).delete() //Clean up last test + + //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions + /* Database is created */ + SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() + + /* Database is closed, e.g. the app has been closed */ + + //////////////////////////////////////////// + + /* Database is opened, not created */ + + db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + } + + @After + fun tearDown() { + if (this::db.isInitialized) { + db.close() + } + } + + private fun createDb(db: SupportSQLiteDatabase) { + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") + + db.version = 1 + } + + private fun modifySchema(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL," + + "`column2` TEXT" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") + db.execSQL("DROP TABLE `TEST_TABLE_OLD`") + db.update( + "TEST_TABLE", + CONFLICT_NONE, + ContentValues().also { it.put("column2", "content2"); }, + null, + null + ) + } + + @Test + fun causeBug() { //If this test is successful, the bug occurred + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + it.moveToFirst() + assertArrayEquals( + arrayOf("column0", "column1"), + it.columnNames + ) //Should be ["column0", "column1", "column2"] + } + } +} \ No newline at end of file From eb02fc0aec6833c82ac3d72378d6f6a8f1f0ed08 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:27:02 +0200 Subject: [PATCH 049/120] Applied code style to BugTest.kt and SmallBugTest.kt *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../android_issue153521693/BugTest.kt | 392 +++++++++--------- .../android_issue153521693/SmallBugTest.kt | 184 ++++---- 2 files changed, 288 insertions(+), 288 deletions(-) diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt index 3d7f4caa1..c0f4db973 100644 --- a/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt @@ -43,204 +43,204 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BugTest { - private val TEST_DB = "bug-test" - - private val context = InstrumentationRegistry.getInstrumentation().context - - private lateinit var db: SupportSQLiteDatabase - - @Before - fun setup() { - context.getDatabasePath(TEST_DB).delete() //Clean up last test - - //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions - /* Database is created */ - SupportSQLiteOpenHelper.Configuration(context, TEST_DB, - object : SupportSQLiteOpenHelper.Callback(1) { - override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) - override fun onUpgrade( - db: SupportSQLiteDatabase, - oldVersion: Int, - newVersion: Int - ) = throw AssertionError() - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() - - /* Database is closed, e.g. the app has been closed */ - - //////////////////////////////////////////// - - /* Database is opened, not created */ - - db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, - object : SupportSQLiteOpenHelper.Callback(1) { - override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() - override fun onUpgrade( - db: SupportSQLiteDatabase, - oldVersion: Int, - newVersion: Int - ) = throw AssertionError() - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } - } - - @After - fun tearDown() { - if (this::db.isInitialized) { - db.close() - } - } - - private fun createDb(db: SupportSQLiteDatabase) { - db.execSQL( - "CREATE TABLE `TEST_TABLE` (" + // - "`column0` TEXT NOT NULL," + - "`column1` TEXT NOT NULL" + - ")" - ) - db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") - - db.version = 1 - } - - private fun modifySchema(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") - db.execSQL( - "CREATE TABLE `TEST_TABLE` (" + // - "`column0` TEXT NOT NULL," + - "`column1` TEXT NOT NULL," + - "`column2` TEXT" + - ")" - ) - db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") - db.execSQL("DROP TABLE `TEST_TABLE_OLD`") - db.update( - "TEST_TABLE", - CONFLICT_NONE, - ContentValues().also { it.put("column2", "content2"); }, - null, - null - ) - } - - private fun assertBroken(cursor: Cursor) { - //cursor.moveToFirst() //This doesn't help because it only fixes *future* cursors - assertEquals(2, cursor.columnCount) //Should be 3! - assertArrayEquals( - arrayOf("column0", "column1"), - cursor.columnNames - ) //Should be ["column0", "column1", "column2"] - assertEquals(-1, cursor.getColumnIndex("column2")) //Should be 2 - } - - private fun assertNotBroken(cursor: Cursor) { - //cursor.moveToFirst() //Not needed and it wouldn't fix this cursor either way. - assertEquals(3, cursor.columnCount) - assertArrayEquals(arrayOf("column0", "column1", "column2"), cursor.columnNames) - assertEquals(2, cursor.getColumnIndex("column2")) - } - - @Test - fun causeBugWithoutBackticks() { - db.query("SELECT * FROM TEST_TABLE").close() - - modifySchema(db) - - db.query("SELECT * FROM TEST_TABLE").use { - assertBroken(it) - } - } - - @Test - fun causeBugWithBackticks() { - db.query("SELECT * FROM `TEST_TABLE`").close() - - modifySchema(db) - - db.query("SELECT * FROM `TEST_TABLE`").use { - assertBroken(it) - } - } - - @Test - fun causeBugWithMove1() { - db.query("SELECT * FROM `TEST_TABLE`").close() - - modifySchema(db) - - db.query("SELECT * FROM `TEST_TABLE`").use { - it.moveToFirst() //This doesn't help because it only fixes *future* cursors - assertBroken(it) - } - - db.query("SELECT * FROM `TEST_TABLE`").use { - //it.moveToFirst() //Not needed and it wouldn't fix this cursor either way. - assertNotBroken(it) - } - } - - @Test - fun causeBugWithMove2() { - db.query("SELECT * FROM TEST_TABLE").close() - db.query("SELECT * FROM `TEST_TABLE`").close() - - modifySchema(db) - - db.query("SELECT * FROM `TEST_TABLE`").use { - assertBroken(it) - } - - db.query("SELECT * FROM TEST_TABLE").use { - it.moveToFirst() //This doesn't help because it only fixes *future* cursors - assertBroken(it) - } - - db.query("SELECT * FROM `TEST_TABLE`").use { - assertBroken(it) - } - } - - @Test - fun doNotCauseBugWithMixedBackticks1() { - db.query("SELECT * FROM `TEST_TABLE`").close() - - modifySchema(db) - - db.query("SELECT * FROM TEST_TABLE").use { - assertNotBroken(it) - } - } - - @Test - fun doNotCauseBugWithMixedBackticks2() { - db.query("SELECT * FROM TEST_TABLE").close() - - modifySchema(db) - - db.query("SELECT * FROM `TEST_TABLE`").use { - assertNotBroken(it) - } - } - - @Test - fun doNotCauseBugWithoutFirstCall() { - modifySchema(db) - - db.query("SELECT * FROM `TEST_TABLE`").use { - assertNotBroken(it) - } - } + private val TEST_DB = "bug-test" + + private val context = InstrumentationRegistry.getInstrumentation().context + + private lateinit var db: SupportSQLiteDatabase + + @Before + fun setup() { + context.getDatabasePath(TEST_DB).delete() //Clean up last test + + //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions + /* Database is created */ + SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() + + /* Database is closed, e.g. the app has been closed */ + + //////////////////////////////////////////// + + /* Database is opened, not created */ + + db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + } + + @After + fun tearDown() { + if (this::db.isInitialized) { + db.close() + } + } + + private fun createDb(db: SupportSQLiteDatabase) { + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") + + db.version = 1 + } + + private fun modifySchema(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL," + + "`column2` TEXT" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") + db.execSQL("DROP TABLE `TEST_TABLE_OLD`") + db.update( + "TEST_TABLE", + CONFLICT_NONE, + ContentValues().also { it.put("column2", "content2"); }, + null, + null + ) + } + + private fun assertBroken(cursor: Cursor) { + //cursor.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertEquals(2, cursor.columnCount) //Should be 3! + assertArrayEquals( + arrayOf("column0", "column1"), + cursor.columnNames + ) //Should be ["column0", "column1", "column2"] + assertEquals(-1, cursor.getColumnIndex("column2")) //Should be 2 + } + + private fun assertNotBroken(cursor: Cursor) { + //cursor.moveToFirst() //Not needed and it wouldn't fix this cursor either way. + assertEquals(3, cursor.columnCount) + assertArrayEquals(arrayOf("column0", "column1", "column2"), cursor.columnNames) + assertEquals(2, cursor.getColumnIndex("column2")) + } + + @Test + fun causeBugWithoutBackticks() { + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + assertBroken(it) + } + } + + @Test + fun causeBugWithBackticks() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + } + + @Test + fun causeBugWithMove1() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + it.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertBroken(it) + } + + db.query("SELECT * FROM `TEST_TABLE`").use { + //it.moveToFirst() //Not needed and it wouldn't fix this cursor either way. + assertNotBroken(it) + } + } + + @Test + fun causeBugWithMove2() { + db.query("SELECT * FROM TEST_TABLE").close() + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + + db.query("SELECT * FROM TEST_TABLE").use { + it.moveToFirst() //This doesn't help because it only fixes *future* cursors + assertBroken(it) + } + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertBroken(it) + } + } + + @Test + fun doNotCauseBugWithMixedBackticks1() { + db.query("SELECT * FROM `TEST_TABLE`").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + assertNotBroken(it) + } + } + + @Test + fun doNotCauseBugWithMixedBackticks2() { + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } + + @Test + fun doNotCauseBugWithoutFirstCall() { + modifySchema(db) + + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } - @Test - fun doNotCauseBugWithRefresh() { - db.query("SELECT * FROM `TEST_TABLE`").close() + @Test + fun doNotCauseBugWithRefresh() { + db.query("SELECT * FROM `TEST_TABLE`").close() - modifySchema(db) + modifySchema(db) - db.query("SELECT * FROM `TEST_TABLE`").use { - it.count //Alternatively: it.moveToNext(), it.moveToFirst() - } + db.query("SELECT * FROM `TEST_TABLE`").use { + it.count //Alternatively: it.moveToNext(), it.moveToFirst() + } - db.query("SELECT * FROM `TEST_TABLE`").use { - assertNotBroken(it) - } - } + db.query("SELECT * FROM `TEST_TABLE`").use { + assertNotBroken(it) + } + } } \ No newline at end of file diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt index 2a467326e..181b83051 100644 --- a/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt @@ -41,96 +41,96 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SmallBugTest { - private val TEST_DB = "small-bug-test" - - private val context = InstrumentationRegistry.getInstrumentation().context - - private lateinit var db: SupportSQLiteDatabase - - @Before - fun setup() { - context.getDatabasePath(TEST_DB).delete() //Clean up last test - - //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions - /* Database is created */ - SupportSQLiteOpenHelper.Configuration(context, TEST_DB, - object : SupportSQLiteOpenHelper.Callback(1) { - override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) - override fun onUpgrade( - db: SupportSQLiteDatabase, - oldVersion: Int, - newVersion: Int - ) = throw AssertionError() - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() - - /* Database is closed, e.g. the app has been closed */ - - //////////////////////////////////////////// - - /* Database is opened, not created */ - - db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, - object : SupportSQLiteOpenHelper.Callback(1) { - override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() - override fun onUpgrade( - db: SupportSQLiteDatabase, - oldVersion: Int, - newVersion: Int - ) = throw AssertionError() - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } - } - - @After - fun tearDown() { - if (this::db.isInitialized) { - db.close() - } - } - - private fun createDb(db: SupportSQLiteDatabase) { - db.execSQL( - "CREATE TABLE `TEST_TABLE` (" + // - "`column0` TEXT NOT NULL," + - "`column1` TEXT NOT NULL" + - ")" - ) - db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") - - db.version = 1 - } - - private fun modifySchema(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") - db.execSQL( - "CREATE TABLE `TEST_TABLE` (" + // - "`column0` TEXT NOT NULL," + - "`column1` TEXT NOT NULL," + - "`column2` TEXT" + - ")" - ) - db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") - db.execSQL("DROP TABLE `TEST_TABLE_OLD`") - db.update( - "TEST_TABLE", - CONFLICT_NONE, - ContentValues().also { it.put("column2", "content2"); }, - null, - null - ) - } - - @Test - fun causeBug() { //If this test is successful, the bug occurred - db.query("SELECT * FROM TEST_TABLE").close() - - modifySchema(db) - - db.query("SELECT * FROM TEST_TABLE").use { - it.moveToFirst() - assertArrayEquals( - arrayOf("column0", "column1"), - it.columnNames - ) //Should be ["column0", "column1", "column2"] - } - } + private val TEST_DB = "small-bug-test" + + private val context = InstrumentationRegistry.getInstrumentation().context + + private lateinit var db: SupportSQLiteDatabase + + @Before + fun setup() { + context.getDatabasePath(TEST_DB).delete() //Clean up last test + + //The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions + /* Database is created */ + SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = createDb(db) + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close() + + /* Database is closed, e.g. the app has been closed */ + + //////////////////////////////////////////// + + /* Database is opened, not created */ + + db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, + object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError() + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) = throw AssertionError() + }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + } + + @After + fun tearDown() { + if (this::db.isInitialized) { + db.close() + } + } + + private fun createDb(db: SupportSQLiteDatabase) { + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', 'content1')") + + db.version = 1 + } + + private fun modifySchema(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`") + db.execSQL( + "CREATE TABLE `TEST_TABLE` (" + // + "`column0` TEXT NOT NULL," + + "`column1` TEXT NOT NULL," + + "`column2` TEXT" + + ")" + ) + db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`") + db.execSQL("DROP TABLE `TEST_TABLE_OLD`") + db.update( + "TEST_TABLE", + CONFLICT_NONE, + ContentValues().also { it.put("column2", "content2"); }, + null, + null + ) + } + + @Test + fun causeBug() { //If this test is successful, the bug occurred + db.query("SELECT * FROM TEST_TABLE").close() + + modifySchema(db) + + db.query("SELECT * FROM TEST_TABLE").use { + it.moveToFirst() + assertArrayEquals( + arrayOf("column0", "column1"), + it.columnNames + ) //Should be ["column0", "column1", "column2"] + } + } } \ No newline at end of file From 7b78b928323d1f593139187f6980f57848b45c12 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:28:50 +0200 Subject: [PATCH 050/120] Finalized work on BugTest.kt and SmallBugTest.kt *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/de/skymatic/android_issue153521693/BugTest.kt | 2 ++ .../java/de/skymatic/android_issue153521693/SmallBugTest.kt | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt index c0f4db973..fa66a4a88 100644 --- a/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/BugTest.kt @@ -40,6 +40,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +// See https://github.com/cryptomator/android/issues/529 to learn more about this class. +// Also see https://github.com/SailReal/Android-Issue-153521693/tree/cfb5033cf287572ac4b4972700f1656ce2b61d03/src/androidTest/java/de/skymatic/android_issue153521693 @RunWith(AndroidJUnit4::class) class BugTest { diff --git a/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt index 181b83051..8de9914f0 100644 --- a/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt +++ b/data/src/androidTest/java/de/skymatic/android_issue153521693/SmallBugTest.kt @@ -31,6 +31,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import org.junit.After import org.junit.Assert.assertArrayEquals @@ -38,7 +39,10 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +// See https://github.com/cryptomator/android/issues/529 to learn more about this class. +// Also see https://github.com/SailReal/Android-Issue-153521693/tree/cfb5033cf287572ac4b4972700f1656ce2b61d03/src/androidTest/java/de/skymatic/android_issue153521693 @RunWith(AndroidJUnit4::class) +@SmallTest class SmallBugTest { private val TEST_DB = "small-bug-test" From 95f65c3ae30ca6e7409385e8b50fc3101aac0e96 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:50:33 +0100 Subject: [PATCH 051/120] Specified journal mode Room is designed to choose the journal mode itself if none is specified. greenDAO's journal mode ("TRUNCATE") will stay for now because the journal mode has influence on the behavior caused by issue #529 [1] and because changing the journal mode complicates the migration further. This can still be changed later. *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index b4288ee8d..b96c03be0 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -51,6 +51,7 @@ class DatabaseModule { .addMigrations(*migrations) // .addCallback(DatabaseCallback) // .openHelperFactory(DatabaseOpenHelperFactory()) // + .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .build() //Fails if no migration is found (especially when downgrading) .also { // //Migrations are only triggered once the database is used for the first time. From ff67959235a76e965cd7545a201acf0fed9367c1 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:00:20 +0100 Subject: [PATCH 052/120] Added first implementation of "CacheControlledSupportSQLiteDatabase" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/DatabaseModule.kt | 3 +- .../db/cachecontrol/AOP_SQLiteDatabase.java | 101 +++++++++ .../CacheControlledSupportSQLiteDatabase.kt | 195 ++++++++++++++++++ 3 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java create mode 100644 data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index b96c03be0..802775eb8 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -8,6 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.data.db.cachecontrol.CacheControlledSupportSQLiteOpenHelperFactory import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -50,7 +51,7 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(DatabaseOpenHelperFactory()) // + .openHelperFactory(CacheControlledSupportSQLiteOpenHelperFactory(DatabaseOpenHelperFactory())) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .build() //Fails if no migration is found (especially when downgrading) .also { // diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java b/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java new file mode 100644 index 000000000..f63c34482 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ** Notice of Modification ** + * + * This file has been altered from its original version by the Cryptomator team. + * For a detailed history of modifications, please refer to the version control log. + * + * The original file can be found at https://android.googlesource.com/platform/frameworks/base/+/7d3ffbae618e9e728644a96647ed709bf39ae75/core/java/android/database/sqlite/SQLiteDatabase.java + * + * -- + * + * https://cryptomator.org/ + */ + +package org.cryptomator.data.db.cachecontrol; + +import android.content.ContentValues; +import android.os.Build; + +final class AOP_SQLiteDatabase { + + public static final String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + + public InsertStatement insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { + //acquireReference(); + try { + StringBuilder sql = new StringBuilder(); + sql.append("INSERT"); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(" INTO "); + sql.append(table); + sql.append('('); + + Object[] bindArgs = null; + //int size = (initialValues != null && !initialValues.isEmpty()) ? initialValues.size() : 0; + int size = (initialValues != null && !isEmpty(initialValues)) ? initialValues.size() : 0; + if (size > 0) { + bindArgs = new Object[size]; + int i = 0; + for (String colName : initialValues.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = initialValues.get(colName); + } + sql.append(')'); + sql.append(" VALUES ("); + for (i = 0; i < size; i++) { + sql.append((i > 0) ? ",?" : "?"); + } + } else { + sql.append(nullColumnHack).append(") VALUES (NULL"); + } + sql.append(')'); + + return new InsertStatement(sql.toString(), bindArgs); + } finally { + //releaseReference(); + } + } + + private boolean isEmpty(ContentValues contentValues) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return contentValues.isEmpty(); + } else { + return contentValues.size() == 0; + } + } + + public static class InsertStatement { + + private final String sql; + private final Object[] bindArgs; + + public InsertStatement(String sql, Object[] bindArgs) { + this.sql = sql; + this.bindArgs = bindArgs; + } + + public String getSql() { + return sql; + } + + public Object[] getBindArgs() { + return bindArgs; + } + } +} + diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt new file mode 100644 index 000000000..e6edf9c03 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt @@ -0,0 +1,195 @@ +package org.cryptomator.data.db.cachecontrol + +import android.content.ContentValues +import android.database.Cursor +import android.os.CancellationSignal +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.SupportSQLiteQuery +import androidx.sqlite.db.SupportSQLiteStatement +import java.util.Collections +import java.util.UUID + +internal class CacheControlledSupportSQLiteDatabase( + private val delegate: SupportSQLiteDatabase +) : SupportSQLiteDatabase by delegate { + + override fun execSQL(sql: String) { + return delegate.execSQL(fix(sql)) + } + + override fun execSQL(sql: String, bindArgs: Array) { + return delegate.execSQL(fix(sql), bindArgs) + } + + override fun query(query: SupportSQLiteQuery): Cursor { + return delegate.query(fix(query)) + } + + override fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor { + return delegate.query(fix(query), cancellationSignal) + } + + override fun query(query: String): Cursor { + return delegate.query(fix(query)) + } + + override fun query(query: String, bindArgs: Array): Cursor { + return delegate.query(fix(query), bindArgs) + } + + override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long { + val processed = helper.insertWithOnConflict(table, null, values, conflictAlgorithm) + val statement = fixCompile(processed.sql, delegate) + SimpleSQLiteQuery.bind(statement, processed.bindArgs) + + return statement.executeInsert() + } + + override fun update( + table: String, + conflictAlgorithm: Int, + values: ContentValues, + whereClause: String?, + whereArgs: Array? + ): Int { + return delegate.update(table, conflictAlgorithm, values, fixWhereClause(whereClause), whereArgs) + } + + override fun delete(table: String, whereClause: String?, whereArgs: Array?): Int { + return delegate.delete(table, fixWhereClause(whereClause), whereArgs) + } + + override fun execPerConnectionSQL(sql: String, bindArgs: Array?) { + delegate.execPerConnectionSQL(fix(sql), bindArgs) + } + + override fun compileStatement(sql: String): SupportSQLiteStatement { + return fixCompile(sql, delegate) + } +} + +private val helper = AOP_SQLiteDatabase() + +private val newIdentifier: String + get() = UUID.randomUUID().toString() + +private fun fix(sql: String, statementIdentifier: String = newIdentifier): String { + return "$sql -- $statementIdentifier" +} + +private fun fix(query: SupportSQLiteQuery): SupportSQLiteQuery { + return CacheControlledSupportSQLiteQuery(query) +} + +private fun fixCompile(sql: String, compilerDelegate: SupportSQLiteDatabase): SupportSQLiteStatement { + return CacheControlledSupportSQLiteStatement(sql, compilerDelegate) +} + +private fun fixWhereClause(whereClause: String?): String { + if (whereClause != null && whereClause.isBlank()) { + throw IllegalArgumentException() + } + return fix(whereClause ?: "1 = 1") +} + +private class CacheControlledSupportSQLiteStatement( + private val sql: String, + private val delegate: SupportSQLiteDatabase, //This is *not* the owning database, but *the delegate of* the owning database +) : SupportSQLiteStatement { + + private val bindings = mutableListOf<(SupportSQLiteStatement) -> Unit>() + + override fun bindBlob(index: Int, value: ByteArray) { + bindings.add { statement -> statement.bindBlob(index, value) } + } + + override fun bindDouble(index: Int, value: Double) { + bindings.add { statement -> statement.bindDouble(index, value) } + } + + override fun bindLong(index: Int, value: Long) { + bindings.add { statement -> statement.bindLong(index, value) } + } + + override fun bindNull(index: Int) { + bindings.add { statement -> statement.bindNull(index) } + } + + override fun bindString(index: Int, value: String) { + bindings.add { statement -> statement.bindString(index, value) } + } + + override fun clearBindings() { + bindings.clear() + } + + override fun close() { + //NO-OP + } + + override fun execute() { + newBoundStatement().use { it.execute() } + } + + override fun executeInsert(): Long { + return newBoundStatement().use { it.executeInsert() } + } + + override fun executeUpdateDelete(): Int { + return newBoundStatement().use { it.executeUpdateDelete() } + } + + override fun simpleQueryForLong(): Long { + return newBoundStatement().use { it.simpleQueryForLong() } + } + + override fun simpleQueryForString(): String? { + return newBoundStatement().use { it.simpleQueryForString() } + } + + private fun newBoundStatement(): SupportSQLiteStatement { + return delegate.compileStatement(fix(sql)).also { statement -> + for (binding: (SupportSQLiteStatement) -> Unit in bindings) { + binding(statement) + } + } + } +} + +private class CacheControlledSupportSQLiteQuery( + private val delegate: SupportSQLiteQuery +) : SupportSQLiteQuery by delegate { + + private val identifiers: MutableMap = Collections.synchronizedMap(mutableMapOf()) + + override val sql: String + get() = fix(delegate.sql, identifiers.computeIfAbsent(delegate.sql) { newIdentifier }) +} + +private class CacheControlledSupportSQLiteOpenHelper( + private val delegate: SupportSQLiteOpenHelper +) : SupportSQLiteOpenHelper by delegate { + + override val writableDatabase: SupportSQLiteDatabase + get() = CacheControlledSupportSQLiteDatabase(delegate.writableDatabase) + + override val readableDatabase: SupportSQLiteDatabase + get() = CacheControlledSupportSQLiteDatabase(delegate.readableDatabase) +} + +class CacheControlledSupportSQLiteOpenHelperFactory( + private val delegate: SupportSQLiteOpenHelper.Factory +) : SupportSQLiteOpenHelper.Factory { + + override fun create( + configuration: SupportSQLiteOpenHelper.Configuration + ): SupportSQLiteOpenHelper { + return CacheControlledSupportSQLiteOpenHelper(delegate.create(configuration)) + } +} + +fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory { + return CacheControlledSupportSQLiteOpenHelperFactory(this) +} \ No newline at end of file From 1afa7b7c56dffa8860a758e336108897227ac607 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:32:04 +0100 Subject: [PATCH 053/120] Refactored "CacheControlledSupportSQLiteDatabase" Replaced particular implementation of "fix" with call to freshly introduced, user-supplied mapping function (and added "RandomUUIDSQLMappingFunction" as new mapping function with that particular implementation) Replaced "fixCompile" with direct instantiation of "CacheControlledSupportSQLiteStatement" Updated static elements to be inner members of "CacheControlledSupportSQLiteDatabase" (and removed now unnecessary properties) Changed computation logic of "sql" property in "CacheControlledSupportSQLiteQuery" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/DatabaseModule.kt | 4 +- .../CacheControlledSupportSQLiteDatabase.kt | 173 +++++++++--------- 2 files changed, 92 insertions(+), 85 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 802775eb8..227cd1543 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -8,7 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import org.cryptomator.data.db.cachecontrol.CacheControlledSupportSQLiteOpenHelperFactory +import org.cryptomator.data.db.cachecontrol.asCacheControlled import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -51,7 +51,7 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(CacheControlledSupportSQLiteOpenHelperFactory(DatabaseOpenHelperFactory())) // + .openHelperFactory(DatabaseOpenHelperFactory().asCacheControlled()) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .build() //Fails if no migration is found (especially when downgrading) .also { // diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt index e6edf9c03..085df98e5 100644 --- a/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt @@ -12,7 +12,8 @@ import java.util.Collections import java.util.UUID internal class CacheControlledSupportSQLiteDatabase( - private val delegate: SupportSQLiteDatabase + private val delegate: SupportSQLiteDatabase, + private val mappingFunction: SQLMappingFunction ) : SupportSQLiteDatabase by delegate { override fun execSQL(sql: String) { @@ -41,7 +42,7 @@ internal class CacheControlledSupportSQLiteDatabase( override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long { val processed = helper.insertWithOnConflict(table, null, values, conflictAlgorithm) - val statement = fixCompile(processed.sql, delegate) + val statement = CacheControlledSupportSQLiteStatement(processed.sql) SimpleSQLiteQuery.bind(statement, processed.bindArgs) return statement.executeInsert() @@ -66,130 +67,136 @@ internal class CacheControlledSupportSQLiteDatabase( } override fun compileStatement(sql: String): SupportSQLiteStatement { - return fixCompile(sql, delegate) + return CacheControlledSupportSQLiteStatement(sql) } -} - -private val helper = AOP_SQLiteDatabase() - -private val newIdentifier: String - get() = UUID.randomUUID().toString() -private fun fix(sql: String, statementIdentifier: String = newIdentifier): String { - return "$sql -- $statementIdentifier" -} + private val helper = AOP_SQLiteDatabase() -private fun fix(query: SupportSQLiteQuery): SupportSQLiteQuery { - return CacheControlledSupportSQLiteQuery(query) -} + private fun fix(sql: String): String { + return mappingFunction(sql) + } -private fun fixCompile(sql: String, compilerDelegate: SupportSQLiteDatabase): SupportSQLiteStatement { - return CacheControlledSupportSQLiteStatement(sql, compilerDelegate) -} + private fun fix(query: SupportSQLiteQuery): SupportSQLiteQuery { + return CacheControlledSupportSQLiteQuery(query) + } -private fun fixWhereClause(whereClause: String?): String { - if (whereClause != null && whereClause.isBlank()) { - throw IllegalArgumentException() + private fun fixWhereClause(whereClause: String?): String { + if (whereClause != null && whereClause.isBlank()) { + throw IllegalArgumentException() + } + return fix(whereClause ?: "1 = 1") } - return fix(whereClause ?: "1 = 1") -} -private class CacheControlledSupportSQLiteStatement( - private val sql: String, - private val delegate: SupportSQLiteDatabase, //This is *not* the owning database, but *the delegate of* the owning database -) : SupportSQLiteStatement { + private inner class CacheControlledSupportSQLiteStatement( + private val sql: String + ) : SupportSQLiteStatement { - private val bindings = mutableListOf<(SupportSQLiteStatement) -> Unit>() + private val bindings = mutableListOf<(SupportSQLiteStatement) -> Unit>() - override fun bindBlob(index: Int, value: ByteArray) { - bindings.add { statement -> statement.bindBlob(index, value) } - } + override fun bindBlob(index: Int, value: ByteArray) { + bindings.add { statement -> statement.bindBlob(index, value) } + } - override fun bindDouble(index: Int, value: Double) { - bindings.add { statement -> statement.bindDouble(index, value) } - } + override fun bindDouble(index: Int, value: Double) { + bindings.add { statement -> statement.bindDouble(index, value) } + } - override fun bindLong(index: Int, value: Long) { - bindings.add { statement -> statement.bindLong(index, value) } - } + override fun bindLong(index: Int, value: Long) { + bindings.add { statement -> statement.bindLong(index, value) } + } - override fun bindNull(index: Int) { - bindings.add { statement -> statement.bindNull(index) } - } + override fun bindNull(index: Int) { + bindings.add { statement -> statement.bindNull(index) } + } - override fun bindString(index: Int, value: String) { - bindings.add { statement -> statement.bindString(index, value) } - } + override fun bindString(index: Int, value: String) { + bindings.add { statement -> statement.bindString(index, value) } + } - override fun clearBindings() { - bindings.clear() - } + override fun clearBindings() { + bindings.clear() + } - override fun close() { - //NO-OP - } + override fun close() { + //NO-OP + } - override fun execute() { - newBoundStatement().use { it.execute() } - } + override fun execute() { + newBoundStatement().use { it.execute() } + } - override fun executeInsert(): Long { - return newBoundStatement().use { it.executeInsert() } - } + override fun executeInsert(): Long { + return newBoundStatement().use { it.executeInsert() } + } - override fun executeUpdateDelete(): Int { - return newBoundStatement().use { it.executeUpdateDelete() } - } + override fun executeUpdateDelete(): Int { + return newBoundStatement().use { it.executeUpdateDelete() } + } - override fun simpleQueryForLong(): Long { - return newBoundStatement().use { it.simpleQueryForLong() } - } + override fun simpleQueryForLong(): Long { + return newBoundStatement().use { it.simpleQueryForLong() } + } - override fun simpleQueryForString(): String? { - return newBoundStatement().use { it.simpleQueryForString() } - } + override fun simpleQueryForString(): String? { + return newBoundStatement().use { it.simpleQueryForString() } + } - private fun newBoundStatement(): SupportSQLiteStatement { - return delegate.compileStatement(fix(sql)).also { statement -> - for (binding: (SupportSQLiteStatement) -> Unit in bindings) { - binding(statement) + private fun newBoundStatement(): SupportSQLiteStatement { + return delegate.compileStatement(fix(sql)).also { statement -> + for (binding: (SupportSQLiteStatement) -> Unit in bindings) { + binding(statement) + } } } } -} -private class CacheControlledSupportSQLiteQuery( - private val delegate: SupportSQLiteQuery -) : SupportSQLiteQuery by delegate { + private inner class CacheControlledSupportSQLiteQuery( + private val delegateQuery: SupportSQLiteQuery + ) : SupportSQLiteQuery by delegateQuery { - private val identifiers: MutableMap = Collections.synchronizedMap(mutableMapOf()) + private val identifiers: MutableMap = Collections.synchronizedMap(mutableMapOf()) - override val sql: String - get() = fix(delegate.sql, identifiers.computeIfAbsent(delegate.sql) { newIdentifier }) + override val sql: String + get() = identifiers.computeIfAbsent(delegateQuery.sql) { fix(it) } + } } private class CacheControlledSupportSQLiteOpenHelper( - private val delegate: SupportSQLiteOpenHelper + private val delegate: SupportSQLiteOpenHelper, + private val mappingFunction: SQLMappingFunction ) : SupportSQLiteOpenHelper by delegate { override val writableDatabase: SupportSQLiteDatabase - get() = CacheControlledSupportSQLiteDatabase(delegate.writableDatabase) + get() = CacheControlledSupportSQLiteDatabase(delegate.writableDatabase, mappingFunction) override val readableDatabase: SupportSQLiteDatabase - get() = CacheControlledSupportSQLiteDatabase(delegate.readableDatabase) + get() = CacheControlledSupportSQLiteDatabase(delegate.readableDatabase, mappingFunction) } -class CacheControlledSupportSQLiteOpenHelperFactory( - private val delegate: SupportSQLiteOpenHelper.Factory +private class CacheControlledSupportSQLiteOpenHelperFactory( + private val delegate: SupportSQLiteOpenHelper.Factory, + private val mappingFunction: SQLMappingFunction ) : SupportSQLiteOpenHelper.Factory { override fun create( configuration: SupportSQLiteOpenHelper.Configuration ): SupportSQLiteOpenHelper { - return CacheControlledSupportSQLiteOpenHelper(delegate.create(configuration)) + return CacheControlledSupportSQLiteOpenHelper(delegate.create(configuration), mappingFunction) } } -fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory { - return CacheControlledSupportSQLiteOpenHelperFactory(this) +fun SupportSQLiteOpenHelper.Factory.asCacheControlled(mappingFunction: SQLMappingFunction = RandomUUIDSQLMappingFunction): SupportSQLiteOpenHelper.Factory { + return CacheControlledSupportSQLiteOpenHelperFactory(this, mappingFunction) +} + +interface SQLMappingFunction : (String) -> String + +object RandomUUIDSQLMappingFunction : SQLMappingFunction { + + private val newIdentifier: String + get() = UUID.randomUUID().toString() + + override fun invoke(sql: String): String { + return "$sql -- $newIdentifier" + } } \ No newline at end of file From 7d5de82734fdeebed78a0514c61a9e27b3ac7415 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:01:24 +0100 Subject: [PATCH 054/120] Narrowed visibility for members of "AOP_SQLiteDatabase" class *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/cachecontrol/AOP_SQLiteDatabase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java b/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java index f63c34482..e4943e6b9 100644 --- a/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java +++ b/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java @@ -32,9 +32,9 @@ final class AOP_SQLiteDatabase { - public static final String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + private static final String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; - public InsertStatement insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { + InsertStatement insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { //acquireReference(); try { StringBuilder sql = new StringBuilder(); @@ -79,7 +79,7 @@ private boolean isEmpty(ContentValues contentValues) { } } - public static class InsertStatement { + static class InsertStatement { private final String sql; private final Object[] bindArgs; From 13261d6259d00671cf740a039bd4bafe1408fabd Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:14:00 +0100 Subject: [PATCH 055/120] Renamed parts of cache-control Renamed package "org.cryptomator.data.db.cachecontrol" to "org.cryptomator.data.db.sqlmapping" Renamed "CacheControlledSupportSQLiteDatabase.kt" to "MappingSupportSQLiteDatabase.kt" Moved contents to new location *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt | 2 +- .../db/{cachecontrol => sqlmapping}/AOP_SQLiteDatabase.java | 2 +- .../MappingSupportSQLiteDatabase.kt} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename data/src/main/java/org/cryptomator/data/db/{cachecontrol => sqlmapping}/AOP_SQLiteDatabase.java (98%) rename data/src/main/java/org/cryptomator/data/db/{cachecontrol/CacheControlledSupportSQLiteDatabase.kt => sqlmapping/MappingSupportSQLiteDatabase.kt} (99%) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 227cd1543..1ece38103 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -8,7 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import org.cryptomator.data.db.cachecontrol.asCacheControlled +import org.cryptomator.data.db.sqlmapping.asCacheControlled import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java b/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java similarity index 98% rename from data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java rename to data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java index e4943e6b9..dd78c0fe3 100644 --- a/data/src/main/java/org/cryptomator/data/db/cachecontrol/AOP_SQLiteDatabase.java +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java @@ -25,7 +25,7 @@ * https://cryptomator.org/ */ -package org.cryptomator.data.db.cachecontrol; +package org.cryptomator.data.db.sqlmapping; import android.content.ContentValues; import android.os.Build; diff --git a/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt similarity index 99% rename from data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt rename to data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 085df98e5..bc1b0c046 100644 --- a/data/src/main/java/org/cryptomator/data/db/cachecontrol/CacheControlledSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.db.cachecontrol +package org.cryptomator.data.db.sqlmapping import android.content.ContentValues import android.database.Cursor From 42f572ace0338d26b1d918d51dc4b69271fc0ceb Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:28:19 +0100 Subject: [PATCH 056/120] Renamed class "CacheControlledSupportSQLiteDatabase" and its members Renamed class "CacheControlledSupportSQLiteDatabase" to "MappingSupportSQLiteDatabase" Renamed class "CacheControlledSupportSQLiteOpenHelper" to "MappingSupportSQLiteOpenHelper" Renamed class "CacheControlledSupportSQLiteOpenHelperFactory" to "MappingSupportSQLiteOpenHelperFactory" Renamed inner class "CacheControlledSupportSQLiteStatement" to "MappingSupportSQLiteStatement" Renamed inner class "CacheControlledSupportSQLiteQuery" to "MappingSupportSQLiteQuery" Renamed method "fix" to "map" in "MappingSupportSQLiteDatabase" Renamed method "fixWhereClause" to "mapWhereClause" in "MappingSupportSQLiteDatabase" Renamed property "identifiers" to "mappedQueries" in "MappingSupportSQLiteQuery" Renamed extension method "SupportSQLiteOpenHelper.Factory.asCacheControlled" to "SupportSQLiteOpenHelper.Factory.asMapped" in "MappingSupportSQLiteDatabase.kt" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/DatabaseModule.kt | 4 +- .../MappingSupportSQLiteDatabase.kt | 58 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 1ece38103..261611cab 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -8,7 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import org.cryptomator.data.db.sqlmapping.asCacheControlled +import org.cryptomator.data.db.sqlmapping.asMapped import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -51,7 +51,7 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(DatabaseOpenHelperFactory().asCacheControlled()) // + .openHelperFactory(DatabaseOpenHelperFactory().asMapped()) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .build() //Fails if no migration is found (especially when downgrading) .also { // diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index bc1b0c046..e44e67464 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -11,38 +11,38 @@ import androidx.sqlite.db.SupportSQLiteStatement import java.util.Collections import java.util.UUID -internal class CacheControlledSupportSQLiteDatabase( +internal class MappingSupportSQLiteDatabase( private val delegate: SupportSQLiteDatabase, private val mappingFunction: SQLMappingFunction ) : SupportSQLiteDatabase by delegate { override fun execSQL(sql: String) { - return delegate.execSQL(fix(sql)) + return delegate.execSQL(map(sql)) } override fun execSQL(sql: String, bindArgs: Array) { - return delegate.execSQL(fix(sql), bindArgs) + return delegate.execSQL(map(sql), bindArgs) } override fun query(query: SupportSQLiteQuery): Cursor { - return delegate.query(fix(query)) + return delegate.query(map(query)) } override fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor { - return delegate.query(fix(query), cancellationSignal) + return delegate.query(map(query), cancellationSignal) } override fun query(query: String): Cursor { - return delegate.query(fix(query)) + return delegate.query(map(query)) } override fun query(query: String, bindArgs: Array): Cursor { - return delegate.query(fix(query), bindArgs) + return delegate.query(map(query), bindArgs) } override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long { val processed = helper.insertWithOnConflict(table, null, values, conflictAlgorithm) - val statement = CacheControlledSupportSQLiteStatement(processed.sql) + val statement = MappingSupportSQLiteStatement(processed.sql) SimpleSQLiteQuery.bind(statement, processed.bindArgs) return statement.executeInsert() @@ -55,39 +55,39 @@ internal class CacheControlledSupportSQLiteDatabase( whereClause: String?, whereArgs: Array? ): Int { - return delegate.update(table, conflictAlgorithm, values, fixWhereClause(whereClause), whereArgs) + return delegate.update(table, conflictAlgorithm, values, mapWhereClause(whereClause), whereArgs) } override fun delete(table: String, whereClause: String?, whereArgs: Array?): Int { - return delegate.delete(table, fixWhereClause(whereClause), whereArgs) + return delegate.delete(table, mapWhereClause(whereClause), whereArgs) } override fun execPerConnectionSQL(sql: String, bindArgs: Array?) { - delegate.execPerConnectionSQL(fix(sql), bindArgs) + delegate.execPerConnectionSQL(map(sql), bindArgs) } override fun compileStatement(sql: String): SupportSQLiteStatement { - return CacheControlledSupportSQLiteStatement(sql) + return MappingSupportSQLiteStatement(sql) } private val helper = AOP_SQLiteDatabase() - private fun fix(sql: String): String { + private fun map(sql: String): String { return mappingFunction(sql) } - private fun fix(query: SupportSQLiteQuery): SupportSQLiteQuery { - return CacheControlledSupportSQLiteQuery(query) + private fun map(query: SupportSQLiteQuery): SupportSQLiteQuery { + return MappingSupportSQLiteQuery(query) } - private fun fixWhereClause(whereClause: String?): String { + private fun mapWhereClause(whereClause: String?): String { if (whereClause != null && whereClause.isBlank()) { throw IllegalArgumentException() } - return fix(whereClause ?: "1 = 1") + return map(whereClause ?: "1 = 1") } - private inner class CacheControlledSupportSQLiteStatement( + private inner class MappingSupportSQLiteStatement( private val sql: String ) : SupportSQLiteStatement { @@ -142,7 +142,7 @@ internal class CacheControlledSupportSQLiteDatabase( } private fun newBoundStatement(): SupportSQLiteStatement { - return delegate.compileStatement(fix(sql)).also { statement -> + return delegate.compileStatement(map(sql)).also { statement -> for (binding: (SupportSQLiteStatement) -> Unit in bindings) { binding(statement) } @@ -150,30 +150,30 @@ internal class CacheControlledSupportSQLiteDatabase( } } - private inner class CacheControlledSupportSQLiteQuery( + private inner class MappingSupportSQLiteQuery( private val delegateQuery: SupportSQLiteQuery ) : SupportSQLiteQuery by delegateQuery { - private val identifiers: MutableMap = Collections.synchronizedMap(mutableMapOf()) + private val mappedQueries: MutableMap = Collections.synchronizedMap(mutableMapOf()) override val sql: String - get() = identifiers.computeIfAbsent(delegateQuery.sql) { fix(it) } + get() = mappedQueries.computeIfAbsent(delegateQuery.sql) { map(it) } } } -private class CacheControlledSupportSQLiteOpenHelper( +private class MappingSupportSQLiteOpenHelper( private val delegate: SupportSQLiteOpenHelper, private val mappingFunction: SQLMappingFunction ) : SupportSQLiteOpenHelper by delegate { override val writableDatabase: SupportSQLiteDatabase - get() = CacheControlledSupportSQLiteDatabase(delegate.writableDatabase, mappingFunction) + get() = MappingSupportSQLiteDatabase(delegate.writableDatabase, mappingFunction) override val readableDatabase: SupportSQLiteDatabase - get() = CacheControlledSupportSQLiteDatabase(delegate.readableDatabase, mappingFunction) + get() = MappingSupportSQLiteDatabase(delegate.readableDatabase, mappingFunction) } -private class CacheControlledSupportSQLiteOpenHelperFactory( +private class MappingSupportSQLiteOpenHelperFactory( private val delegate: SupportSQLiteOpenHelper.Factory, private val mappingFunction: SQLMappingFunction ) : SupportSQLiteOpenHelper.Factory { @@ -181,12 +181,12 @@ private class CacheControlledSupportSQLiteOpenHelperFactory( override fun create( configuration: SupportSQLiteOpenHelper.Configuration ): SupportSQLiteOpenHelper { - return CacheControlledSupportSQLiteOpenHelper(delegate.create(configuration), mappingFunction) + return MappingSupportSQLiteOpenHelper(delegate.create(configuration), mappingFunction) } } -fun SupportSQLiteOpenHelper.Factory.asCacheControlled(mappingFunction: SQLMappingFunction = RandomUUIDSQLMappingFunction): SupportSQLiteOpenHelper.Factory { - return CacheControlledSupportSQLiteOpenHelperFactory(this, mappingFunction) +fun SupportSQLiteOpenHelper.Factory.asMapped(mappingFunction: SQLMappingFunction = RandomUUIDSQLMappingFunction): SupportSQLiteOpenHelper.Factory { + return MappingSupportSQLiteOpenHelperFactory(this, mappingFunction) } interface SQLMappingFunction : (String) -> String From db2edda3645d5f795efaf52fc74b1eefd70b77b5 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:54:45 +0100 Subject: [PATCH 057/120] Reorganized cache-control to improve abstraction Moved "RandomUUIDSQLMappingFunction" from "MappingSupportSQLiteDatabase.kt" to new file "SQLiteCacheControl.kt" Added extension function "SupportSQLiteOpenHelper.Factory.asCacheControlled" in "SQLiteCacheControl.kt" as wrapper for "asMapped" with "RandomUUIDSQLMappingFunction" as mapping function Removed "RandomUUIDSQLMappingFunction" as default mapping function from extension function "SupportSQLiteOpenHelper.Factory.asMapped" in "MappingSupportSQLiteDatabase.kt" Replaced call to "asMapped" with call to "asCacheControlled" in "DatabaseModule" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/DatabaseModule.kt | 4 ++-- .../cryptomator/data/db/SQLiteCacheControl.kt | 21 +++++++++++++++++++ .../MappingSupportSQLiteDatabase.kt | 15 ++----------- 3 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 261611cab..06a7bb2fd 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -8,7 +8,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import org.cryptomator.data.db.sqlmapping.asMapped +import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -51,7 +51,7 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(DatabaseOpenHelperFactory().asMapped()) // + .openHelperFactory(DatabaseOpenHelperFactory().asCacheControlled()) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .build() //Fails if no migration is found (especially when downgrading) .also { // diff --git a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt new file mode 100644 index 000000000..02f48da2f --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt @@ -0,0 +1,21 @@ +package org.cryptomator.data.db + +import androidx.sqlite.db.SupportSQLiteOpenHelper +import org.cryptomator.data.db.sqlmapping.SQLMappingFunction +import org.cryptomator.data.db.sqlmapping.asMapped +import java.util.UUID + +object SQLiteCacheControl { + + object RandomUUIDSQLMappingFunction : SQLMappingFunction { + + private val newIdentifier: String + get() = UUID.randomUUID().toString() + + override fun invoke(sql: String): String { + return "$sql -- $newIdentifier" + } + } + + fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory = asMapped(RandomUUIDSQLMappingFunction) +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index e44e67464..0eba2530c 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -9,7 +9,6 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement import java.util.Collections -import java.util.UUID internal class MappingSupportSQLiteDatabase( private val delegate: SupportSQLiteDatabase, @@ -185,18 +184,8 @@ private class MappingSupportSQLiteOpenHelperFactory( } } -fun SupportSQLiteOpenHelper.Factory.asMapped(mappingFunction: SQLMappingFunction = RandomUUIDSQLMappingFunction): SupportSQLiteOpenHelper.Factory { +fun SupportSQLiteOpenHelper.Factory.asMapped(mappingFunction: SQLMappingFunction): SupportSQLiteOpenHelper.Factory { return MappingSupportSQLiteOpenHelperFactory(this, mappingFunction) } -interface SQLMappingFunction : (String) -> String - -object RandomUUIDSQLMappingFunction : SQLMappingFunction { - - private val newIdentifier: String - get() = UUID.randomUUID().toString() - - override fun invoke(sql: String): String { - return "$sql -- $newIdentifier" - } -} \ No newline at end of file +interface SQLMappingFunction : (String) -> String \ No newline at end of file From eae6c03d1dc5e7d9b015f9717818d9d3299475ef Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:55:08 +0100 Subject: [PATCH 058/120] Renamed "RandomUUIDSQLMappingFunction" to "RandomUUIDMapping" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../main/java/org/cryptomator/data/db/SQLiteCacheControl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt index 02f48da2f..fdad1f605 100644 --- a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt +++ b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt @@ -7,7 +7,7 @@ import java.util.UUID object SQLiteCacheControl { - object RandomUUIDSQLMappingFunction : SQLMappingFunction { + object RandomUUIDMapping : SQLMappingFunction { private val newIdentifier: String get() = UUID.randomUUID().toString() @@ -17,5 +17,5 @@ object SQLiteCacheControl { } } - fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory = asMapped(RandomUUIDSQLMappingFunction) + fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory = asMapped(RandomUUIDMapping) } \ No newline at end of file From 35b4e00177f80d7e72cc160b707db3c1e45e97b2 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:27:48 +0100 Subject: [PATCH 059/120] Refactored "SQLMappingFunction" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/SQLiteCacheControl.kt | 6 +++++- .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 14 ++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt index fdad1f605..fd540bafe 100644 --- a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt +++ b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt @@ -12,9 +12,13 @@ object SQLiteCacheControl { private val newIdentifier: String get() = UUID.randomUUID().toString() - override fun invoke(sql: String): String { + override fun map(sql: String): String { return "$sql -- $newIdentifier" } + + override fun mapWhereClause(whereClause: String?): String { + return map(whereClause ?: "1 = 1") + } } fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory = asMapped(RandomUUIDMapping) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 0eba2530c..88b8b1401 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -72,18 +72,18 @@ internal class MappingSupportSQLiteDatabase( private val helper = AOP_SQLiteDatabase() private fun map(sql: String): String { - return mappingFunction(sql) + return mappingFunction.map(sql) } private fun map(query: SupportSQLiteQuery): SupportSQLiteQuery { return MappingSupportSQLiteQuery(query) } - private fun mapWhereClause(whereClause: String?): String { + private fun mapWhereClause(whereClause: String?): String? { if (whereClause != null && whereClause.isBlank()) { throw IllegalArgumentException() } - return map(whereClause ?: "1 = 1") + return mappingFunction.mapWhereClause(whereClause) } private inner class MappingSupportSQLiteStatement( @@ -188,4 +188,10 @@ fun SupportSQLiteOpenHelper.Factory.asMapped(mappingFunction: SQLMappingFunction return MappingSupportSQLiteOpenHelperFactory(this, mappingFunction) } -interface SQLMappingFunction : (String) -> String \ No newline at end of file +interface SQLMappingFunction { + + fun map(sql: String): String + + fun mapWhereClause(whereClause: String?): String? + +} \ No newline at end of file From 95eede542291f50fb3334b95be548d01c0aff2bf Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:04:15 +0100 Subject: [PATCH 060/120] Redefined logic of "MappingSupportSQLiteQuery" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 88b8b1401..8f1dedc8e 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -8,7 +8,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement -import java.util.Collections +import timber.log.Timber internal class MappingSupportSQLiteDatabase( private val delegate: SupportSQLiteDatabase, @@ -153,10 +153,18 @@ internal class MappingSupportSQLiteDatabase( private val delegateQuery: SupportSQLiteQuery ) : SupportSQLiteQuery by delegateQuery { - private val mappedQueries: MutableMap = Collections.synchronizedMap(mutableMapOf()) + private val lock = Any() + private var called = false + private val _sql = map(delegateQuery.sql) override val sql: String - get() = mappedQueries.computeIfAbsent(delegateQuery.sql) { map(it) } + get() = synchronized(lock) { + if (called) { + Timber.tag("MappingSupportSQLiteQuery").e("SQL queried twice") + } + called = true + return _sql + } } } From b3449f07510fc86203e6b3ded5cfe3ff72902b01 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:18:00 +0200 Subject: [PATCH 061/120] Re-enabled tests See: c13b12ffc8d441f344793f2436506974b145899f *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index a49364d2e..7d56029e6 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -813,7 +813,7 @@ class UpgradeDatabaseTest { for (preTable in pre13Tables) { preTable.value.use { preCursor -> Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> - //assertCursorEquals(preCursor, postCursor) + assertCursorEquals(preCursor, postCursor) } } } @@ -892,7 +892,7 @@ class UpgradeDatabaseTest { for (preTable in pre13Tables) { preTable.value.use { preCursor -> Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> - //assertCursorEquals(preCursor, postCursor) + assertCursorEquals(preCursor, postCursor) } } } From 6b1a9b5c5154a3c75c6de5cadedf3df26c5d7b6c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 19 Mar 2024 00:00:54 +0100 Subject: [PATCH 062/120] Added first tests for "MappingSupportSQLiteDatabase" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 326 ++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt new file mode 100644 index 000000000..7024d5d7a --- /dev/null +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -0,0 +1,326 @@ +package org.cryptomator.data.db.sqlmapping + +import org.mockito.ArgumentMatchers.argThat as defaultArgThat +import org.mockito.Mockito.`when` as whenCalled +import org.mockito.kotlin.any as reifiedAny +import org.mockito.kotlin.anyOrNull as reifiedAnyOrNull +import org.mockito.kotlin.argThat as reifiedArgThat +import android.database.Cursor +import android.database.MatrixCursor +import android.os.CancellationSignal +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteProgram +import androidx.sqlite.db.SupportSQLiteQuery +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.ArgumentMatcher +import org.mockito.Mockito.anyString +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.anyArray +import org.mockito.kotlin.isNull +import java.util.stream.Stream +import kotlin.streams.asStream + + +class MappingSupportSQLiteDatabaseTest { + + private lateinit var delegateMock: SupportSQLiteDatabase + + private lateinit var identityMapping: MappingSupportSQLiteDatabase + private lateinit var commentMapping: MappingSupportSQLiteDatabase + + @BeforeEach + fun beforeEach() { + delegateMock = mock(SupportSQLiteDatabase::class.java) + + identityMapping = MappingSupportSQLiteDatabase(delegateMock, object : SQLMappingFunction { + override fun map(sql: String): String = sql + override fun mapWhereClause(whereClause: String?): String? = whereClause + }) + commentMapping = MappingSupportSQLiteDatabase(delegateMock, object : SQLMappingFunction { + override fun map(sql: String): String = "$sql -- Comment!" + override fun mapWhereClause(whereClause: String?): String = map(whereClause ?: "1 = 1") + }) + } + + @Test + fun testExecSQL() { + identityMapping.execSQL("INSERT INTO `id_test` (`col`) VALUES ('test1')") + commentMapping.execSQL("INSERT INTO `comment_test` (`col`) VALUES ('test2')") + + verify(delegateMock).execSQL("INSERT INTO `id_test` (`col`) VALUES ('test1')") + verify(delegateMock).execSQL("INSERT INTO `comment_test` (`col`) VALUES ('test2') -- Comment!") + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testExecSQLWithBindings() { + identityMapping.execSQL("INSERT INTO `id_test` (`col`) VALUES (?)", arrayOf("test1")) + commentMapping.execSQL("INSERT INTO `comment_test` (`col`) VALUES (?)", arrayOf("test2")) + + verify(delegateMock).execSQL("INSERT INTO `id_test` (`col`) VALUES (?)", arrayOf("test1")) + verify(delegateMock).execSQL("INSERT INTO `comment_test` (`col`) VALUES (?) -- Comment!", arrayOf("test2")) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testQueryString() { + whenCalled(delegateMock.query(anyString())).thenReturn(DUMMY_CURSOR) + + identityMapping.query("SELECT `col` FROM `id_test`") + commentMapping.query("SELECT `col` FROM `comment_test`") + + verify(delegateMock).query("SELECT `col` FROM `id_test`") + verify(delegateMock).query("SELECT `col` FROM `comment_test` -- Comment!") + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testQueryStringWithBindings() { + whenCalled(delegateMock.query(anyString(), anyArray())).thenReturn(DUMMY_CURSOR) + + identityMapping.query("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")) + commentMapping.query("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")) + + verify(delegateMock).query("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")) + verify(delegateMock).query("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) + verifyNoMoreInteractions(delegateMock) + } + + + @Test + fun testQueryBindable() { + whenCalled(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) + + identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test`")) + commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test`")) + + verify(delegateMock).query( + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), supportSQLiteQueryProperties) + ) + verify(delegateMock).query( + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!"), supportSQLiteQueryProperties) + ) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testQueryBindableWithBindings() { + whenCalled(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) + + identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1"))) + commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2"))) + + verify(delegateMock).query( + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), supportSQLiteQueryProperties) + ) + verify(delegateMock).query( + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")), supportSQLiteQueryProperties) + ) + verifyNoMoreInteractions(delegateMock) + } + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestQueryCancelable") + fun testQueryCancelable(queries: DataForTestQueryCancelable, signals: DataForTestQueryCancelable) { + whenCalled(delegateMock.query(reifiedAny(), reifiedAnyOrNull())).thenReturn(DUMMY_CURSOR) + + identityMapping.query(queries.idCall, signals.idCall) + commentMapping.query(queries.commentCall, signals.commentCall) + + verify(delegateMock).query( + anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), + anyPseudoEqualsUnlessNull(signals.idExpected, setOf(CancellationSignal::isCanceled)) + ) + verify(delegateMock).query( + anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), + anyPseudoEqualsUnlessNull(signals.commentExpected, setOf(CancellationSignal::isCanceled)) + ) + verifyNoMoreInteractions(delegateMock) + } + + /* TODO insert, update */ + + @Test + fun testDelete() { + identityMapping.delete("id_test", "`col` = 'test1'", null) + commentMapping.delete("comment_test", "`col` = 'test2'", null) + + verify(delegateMock).delete("id_test", "`col` = 'test1'", null) + verify(delegateMock).delete("comment_test", "`col` = 'test2' -- Comment!", null) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testDeleteWithBindings() { + identityMapping.delete("id_test", "`col` = ?", arrayOf("test1")) + commentMapping.delete("comment_test", "`col` = ?", arrayOf("test2")) + + verify(delegateMock).delete("id_test", "`col` = ?", arrayOf("test1")) + verify(delegateMock).delete("comment_test", "`col` = ? -- Comment!", arrayOf("test2")) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testDeleteNull() { + identityMapping.delete("id_test", null, null) + commentMapping.delete("comment_test", null, null) + + verify(delegateMock).delete("id_test", null, null) + verify(delegateMock).delete("comment_test", "1 = 1 -- Comment!", null) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testDeleteNullWithBindings() { + identityMapping.delete("id_test", null, arrayOf("included but not used id")) + commentMapping.delete("comment_test", null, arrayOf("included but not used comment")) + + verify(delegateMock).delete("id_test", null, arrayOf("included but not used id")) + verify(delegateMock).delete("comment_test", "1 = 1 -- Comment!", arrayOf("included but not used comment")) + verifyNoMoreInteractions(delegateMock) + } + + @Test + fun testExecPerConnectionSQL() { + identityMapping.execPerConnectionSQL("INSERT INTO `id_test` (`col`) VALUES (?)", arrayOf("test1")) + commentMapping.execPerConnectionSQL("INSERT INTO `comment_test` (`col`) VALUES (?)", arrayOf("test2")) + + verify(delegateMock).execPerConnectionSQL("INSERT INTO `id_test` (`col`) VALUES (?)", arrayOf("test1")) + verify(delegateMock).execPerConnectionSQL("INSERT INTO `comment_test` (`col`) VALUES (?) -- Comment!", arrayOf("test2")) + verifyNoMoreInteractions(delegateMock) + } + + /* TODO compileStatement */ +} + +private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set<(T) -> Any?>): T? { + return if (other != null) defaultArgThat(NullHandlingMatcher(pseudoEquals(other, valueExtractors), false)) else isNull() +} + +private inline fun anyPseudoEquals(other: T, valueExtractors: Set<(T) -> Any?>): T { + return reifiedArgThat(pseudoEquals(other, valueExtractors)) +} + +private fun pseudoEquals(other: T, valueExtractors: Set<(T) -> Any?>): ArgumentMatcher { + require(valueExtractors.isNotEmpty()) + return PseudoEqualsMatcher(other, valueExtractors) +} + +private class PseudoEqualsMatcher( + private val other: T, + private val valueExtractors: Set<(T) -> Any?> +) : ArgumentMatcher { + + override fun matches(argument: T): Boolean { + if (argument === other) { + return true + } + return valueExtractors.all { extractor -> extractor(argument) == extractor(other) } + } +} + +private class NullHandlingMatcher( + private val delegate: ArgumentMatcher, + private val matchNull: Boolean +) : ArgumentMatcher { + + override fun matches(argument: T?): Boolean { + if (argument == null) { + return matchNull + } + return delegate.matches(argument) + } +} + +private val supportSQLiteQueryProperties + get() = setOf(SupportSQLiteQuery::sql, SupportSQLiteQuery::argCount, { query: SupportSQLiteQuery -> + CachingSupportSQLiteProgram().also { query.bindTo(it) }.bindings + }) + +private class CachingSupportSQLiteProgram : SupportSQLiteProgram { + + val bindings = mutableMapOf() + override fun bindBlob(index: Int, value: ByteArray) { + bindings[index] = value + } + + override fun bindDouble(index: Int, value: Double) { + bindings[index] = value + } + + override fun bindLong(index: Int, value: Long) { + bindings[index] = value + } + + override fun bindNull(index: Int) { + bindings[index] = Unit + } + + override fun bindString(index: Int, value: String) { + bindings[index] = value + } + + override fun clearBindings() { + bindings.clear() + } + + override fun close() = throw UnsupportedOperationException("Stub!") +} + +private val DUMMY_CURSOR: Cursor + get() = MatrixCursor(arrayOf()) + +private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { + val mock = mock(CancellationSignal::class.java) + whenCalled(mock.isCanceled).thenReturn(isCanceled) + whenCalled(mock.toString()).thenReturn("Mock(isCanceled=$isCanceled)") + return mock +} + +data class DataForTestQueryCancelable( + val idCall: T, + val commentCall: T, + val idExpected: T, + val commentExpected: T +) + +fun sourceForTestQueryCancelable(): Stream { + val queries = sequenceOf( + DataForTestQueryCancelable( + SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), + SimpleSQLiteQuery("SELECT `col` FROM `comment_test`"), + SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!") + ), + DataForTestQueryCancelable( + SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")), + SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) + ) + ) + val signals = sequenceOf>( + DataForTestQueryCancelable( + mockCancellationSignal(true), + mockCancellationSignal(false), + mockCancellationSignal(true), + mockCancellationSignal(false) + ), + DataForTestQueryCancelable( + null, + null, + null, + null + ) + ) + + return queries.flatMap { anyQuery -> + signals.map { anySignal -> Arguments.of(anyQuery, anySignal) } + }.asStream() +} From af17db59942ab2b827345f2919cddc4417713a78 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:28:47 +0200 Subject: [PATCH 063/120] Added support for mapping cursors Calling "requery" on a cursor may cause the driver to re-use queries, which is unwanted behavior. *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/SQLiteCacheControl.kt | 15 +++++++++++++++ .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 14 ++++++++++---- .../MappingSupportSQLiteDatabaseTest.kt | 2 ++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt index fd540bafe..74a3de865 100644 --- a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt +++ b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt @@ -1,5 +1,6 @@ package org.cryptomator.data.db +import android.database.Cursor import androidx.sqlite.db.SupportSQLiteOpenHelper import org.cryptomator.data.db.sqlmapping.SQLMappingFunction import org.cryptomator.data.db.sqlmapping.asMapped @@ -19,7 +20,21 @@ object SQLiteCacheControl { override fun mapWhereClause(whereClause: String?): String { return map(whereClause ?: "1 = 1") } + + override fun mapCursor(cursor: Cursor): Cursor { + return NoRequeryCursor(cursor) + } } fun SupportSQLiteOpenHelper.Factory.asCacheControlled(): SupportSQLiteOpenHelper.Factory = asMapped(RandomUUIDMapping) +} + +private class NoRequeryCursor( + private val delegateCursor: Cursor +) : Cursor by delegateCursor { + + @Deprecated("Deprecated in Java") + override fun requery(): Boolean { + throw UnsupportedOperationException() + } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 8f1dedc8e..7fca3e115 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -24,19 +24,19 @@ internal class MappingSupportSQLiteDatabase( } override fun query(query: SupportSQLiteQuery): Cursor { - return delegate.query(map(query)) + return mapCursor(delegate.query(map(query))) } override fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor { - return delegate.query(map(query), cancellationSignal) + return mapCursor(delegate.query(map(query), cancellationSignal)) } override fun query(query: String): Cursor { - return delegate.query(map(query)) + return mapCursor(delegate.query(map(query))) } override fun query(query: String, bindArgs: Array): Cursor { - return delegate.query(map(query), bindArgs) + return mapCursor(delegate.query(map(query), bindArgs)) } override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long { @@ -79,6 +79,10 @@ internal class MappingSupportSQLiteDatabase( return MappingSupportSQLiteQuery(query) } + private fun mapCursor(cursor: Cursor): Cursor { + return mappingFunction.mapCursor(cursor) + } + private fun mapWhereClause(whereClause: String?): String? { if (whereClause != null && whereClause.isBlank()) { throw IllegalArgumentException() @@ -202,4 +206,6 @@ interface SQLMappingFunction { fun mapWhereClause(whereClause: String?): String? + fun mapCursor(cursor: Cursor): Cursor + } \ No newline at end of file diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 7024d5d7a..680c3a919 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -42,10 +42,12 @@ class MappingSupportSQLiteDatabaseTest { identityMapping = MappingSupportSQLiteDatabase(delegateMock, object : SQLMappingFunction { override fun map(sql: String): String = sql override fun mapWhereClause(whereClause: String?): String? = whereClause + override fun mapCursor(cursor: Cursor): Cursor = cursor }) commentMapping = MappingSupportSQLiteDatabase(delegateMock, object : SQLMappingFunction { override fun map(sql: String): String = "$sql -- Comment!" override fun mapWhereClause(whereClause: String?): String = map(whereClause ?: "1 = 1") + override fun mapCursor(cursor: Cursor): Cursor = cursor //TODO }) } From 34f75afdcf5ef8b716a4aa74e68797abe68a82c9 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:51:27 +0100 Subject: [PATCH 064/120] Renamed "DataForTestQueryCancelable" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 680c3a919..62e150e6d 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -127,9 +127,10 @@ class MappingSupportSQLiteDatabaseTest { ) verifyNoMoreInteractions(delegateMock) } + @ParameterizedTest @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestQueryCancelable") - fun testQueryCancelable(queries: DataForTestQueryCancelable, signals: DataForTestQueryCancelable) { + fun testQueryCancelable(queries: CallData, signals: CallData) { whenCalled(delegateMock.query(reifiedAny(), reifiedAnyOrNull())).thenReturn(DUMMY_CURSOR) identityMapping.query(queries.idCall, signals.idCall) @@ -285,7 +286,7 @@ private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { return mock } -data class DataForTestQueryCancelable( +data class CallData( val idCall: T, val commentCall: T, val idExpected: T, @@ -294,27 +295,27 @@ data class DataForTestQueryCancelable( fun sourceForTestQueryCancelable(): Stream { val queries = sequenceOf( - DataForTestQueryCancelable( + CallData( SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), SimpleSQLiteQuery("SELECT `col` FROM `comment_test`"), SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!") ), - DataForTestQueryCancelable( + CallData( SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")), SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) ) ) - val signals = sequenceOf>( - DataForTestQueryCancelable( + val signals = sequenceOf>( + CallData( mockCancellationSignal(true), mockCancellationSignal(false), mockCancellationSignal(true), mockCancellationSignal(false) ), - DataForTestQueryCancelable( + CallData( null, null, null, From ed0fe82bbc681ffb7db6c9e1813d97e852e04381 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:53:21 +0100 Subject: [PATCH 065/120] Extracted methods "cartesianProduct" and "toArgumentsStream" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 62e150e6d..c17959338 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -308,7 +308,7 @@ fun sourceForTestQueryCancelable(): Stream { SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) ) ) - val signals = sequenceOf>( + val signals = listOf>( CallData( mockCancellationSignal(true), mockCancellationSignal(false), @@ -323,7 +323,14 @@ fun sourceForTestQueryCancelable(): Stream { ) ) - return queries.flatMap { anyQuery -> - signals.map { anySignal -> Arguments.of(anyQuery, anySignal) } - }.asStream() + return queries.cartesianProduct(signals).map { it.toList() }.toArgumentsStream() } + +@JvmName("cartesianProductTwo") +fun Sequence.cartesianProduct(other: Iterable): Sequence> = flatMap { a -> + other.asSequence().map { b -> a to b } +} + +fun Sequence>.toArgumentsStream(): Stream = map { + Arguments { it.toTypedArray() } +}.asStream() \ No newline at end of file From c89f4b1ea09e5be0f7f22fca884300c60694d96d Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:55:53 +0100 Subject: [PATCH 066/120] Added test for "update" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index c17959338..148a8fb61 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -5,6 +5,7 @@ import org.mockito.Mockito.`when` as whenCalled import org.mockito.kotlin.any as reifiedAny import org.mockito.kotlin.anyOrNull as reifiedAnyOrNull import org.mockito.kotlin.argThat as reifiedArgThat +import android.content.ContentValues import android.database.Cursor import android.database.MatrixCursor import android.os.CancellationSignal @@ -23,6 +24,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.kotlin.anyArray +import org.mockito.kotlin.eq import org.mockito.kotlin.isNull import java.util.stream.Stream import kotlin.streams.asStream @@ -147,7 +149,18 @@ class MappingSupportSQLiteDatabaseTest { verifyNoMoreInteractions(delegateMock) } - /* TODO insert, update */ + /* TODO insert */ + + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestUpdate") + fun testUpdate(contentValues: CallData, whereClauses: CallData, whereArgs: CallData?>) { + identityMapping.update("id_test", 1001, contentValues.idCall, whereClauses.idCall, whereArgs.idCall?.toTypedArray()) + commentMapping.update("comment_test", 1002, contentValues.commentCall, whereClauses.commentCall, whereArgs.commentCall?.toTypedArray()) + + verify(delegateMock).update(eq("id_test"), eq(1001), anyPseudoEquals(contentValues.idExpected, contentValuesProperties), eq(whereClauses.idExpected), eq(whereArgs.idExpected?.toTypedArray())) + verify(delegateMock).update(eq("comment_test"), eq(1002), anyPseudoEquals(contentValues.commentExpected, contentValuesProperties), eq(whereClauses.commentExpected), eq(whereArgs.commentExpected?.toTypedArray())) + verifyNoMoreInteractions(delegateMock) + } @Test fun testDelete() { @@ -276,6 +289,11 @@ private class CachingSupportSQLiteProgram : SupportSQLiteProgram { override fun close() = throw UnsupportedOperationException("Stub!") } +private val contentValuesProperties + get() = setOf( + ContentValues::valueSet + ) + private val DUMMY_CURSOR: Cursor get() = MatrixCursor(arrayOf()) @@ -286,6 +304,14 @@ private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { return mock } +private fun mockContentValues(vararg elements: Pair): ContentValues { + val entries = mapOf(*elements) + val mock = mock(ContentValues::class.java) + whenCalled(mock.valueSet()).thenReturn(entries.entries) + whenCalled(mock.toString()).thenReturn("Mock${entries}") + return mock +} + data class CallData( val idCall: T, val commentCall: T, @@ -326,11 +352,63 @@ fun sourceForTestQueryCancelable(): Stream { return queries.cartesianProduct(signals).map { it.toList() }.toArgumentsStream() } +fun sourceForTestUpdate(): Stream { + val contentValues = sequenceOf( + CallData( + mockContentValues("key1" to "value1"), + mockContentValues("key2" to "value2"), + mockContentValues("key1" to "value1"), + mockContentValues("key2" to "value2") + ), + CallData( + mockContentValues("key1" to null), + mockContentValues(), + mockContentValues("key1" to null), + mockContentValues() + ) + ) + val whereClauses = listOf>( + CallData( + "`col1` = ?", + "`col2` = ?", + "`col1` = ?", + "`col2` = ? -- Comment!" + ), + CallData( + null, + null, + null, + "1 = 1 -- Comment!" + ) + ) + val whereArgs = listOf?>>( //Use List instead of Array to make result data more readable + CallData( + listOf(), + null, + listOf(), + null + ), + CallData( + listOf("val1"), + listOf("val2"), + listOf("val1"), + listOf("val2") + ) + ) + + return contentValues.cartesianProduct(whereClauses).cartesianProduct(whereArgs).map { it.toList() }.toArgumentsStream() +} + @JvmName("cartesianProductTwo") fun Sequence.cartesianProduct(other: Iterable): Sequence> = flatMap { a -> other.asSequence().map { b -> a to b } } +@JvmName("cartesianProductThree") +fun Sequence>.cartesianProduct(other: Iterable): Sequence> = flatMap { abPair -> + other.asSequence().map { c -> Triple(abPair.first, abPair.second, c) } +} + fun Sequence>.toArgumentsStream(): Stream = map { Arguments { it.toTypedArray() } }.asStream() \ No newline at end of file From d98ce8b622dc707f394f7f70740ddef9fd595a08 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:52:16 +0100 Subject: [PATCH 067/120] Specified contract for "SupportSQLiteDatabase.insert" Also moved compat method for "ContentValues.isEmpty" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/sqlmapping/AOP_SQLiteDatabase.java | 13 +++++-------- .../org/cryptomator/data/db/sqlmapping/Helpers.kt | 12 ++++++++++++ .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 7 +++++++ 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java b/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java index dd78c0fe3..b297828b2 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/AOP_SQLiteDatabase.java @@ -28,7 +28,8 @@ package org.cryptomator.data.db.sqlmapping; import android.content.ContentValues; -import android.os.Build; + +import static org.cryptomator.data.db.sqlmapping.HelpersKt.compatIsEmpty; final class AOP_SQLiteDatabase { @@ -46,7 +47,7 @@ InsertStatement insertWithOnConflict(String table, String nullColumnHack, Conten Object[] bindArgs = null; //int size = (initialValues != null && !initialValues.isEmpty()) ? initialValues.size() : 0; - int size = (initialValues != null && !isEmpty(initialValues)) ? initialValues.size() : 0; + int size = (initialValues != null && !compatIsEmpty(initialValues)) ? initialValues.size() : 0; if (size > 0) { bindArgs = new Object[size]; int i = 0; @@ -71,12 +72,8 @@ InsertStatement insertWithOnConflict(String table, String nullColumnHack, Conten } } - private boolean isEmpty(ContentValues contentValues) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - return contentValues.isEmpty(); - } else { - return contentValues.size() == 0; - } + boolean isValidConflictAlgorithm(int conflictAlgorithm) { + return conflictAlgorithm >= 0 && conflictAlgorithm < CONFLICT_VALUES.length; } static class InsertStatement { diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt new file mode 100644 index 000000000..8c55d254f --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt @@ -0,0 +1,12 @@ +package org.cryptomator.data.db.sqlmapping + +import android.content.ContentValues +import android.os.Build + +internal fun ContentValues.compatIsEmpty(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + isEmpty + } else { + size() == 0 + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 7fca3e115..1ba009fd2 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -2,6 +2,7 @@ package org.cryptomator.data.db.sqlmapping import android.content.ContentValues import android.database.Cursor +import android.database.sqlite.SQLiteException import android.os.CancellationSignal import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase @@ -40,6 +41,12 @@ internal class MappingSupportSQLiteDatabase( } override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long { + if (values.compatIsEmpty()) { + throw SQLiteException("Can't insert empty set of values") + } + if (!helper.isValidConflictAlgorithm(conflictAlgorithm)) { + throw SQLiteException("Invalid conflict algorithm") + } val processed = helper.insertWithOnConflict(table, null, values, conflictAlgorithm) val statement = MappingSupportSQLiteStatement(processed.sql) SimpleSQLiteQuery.bind(statement, processed.bindArgs) From bfcca43580c0cb0c78825694f7c471d416880654 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 20 Mar 2024 00:29:35 +0100 Subject: [PATCH 068/120] Added tests for "insert" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 241 +++++++++++++++++- 1 file changed, 238 insertions(+), 3 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 148a8fb61..91f18a229 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -8,24 +8,37 @@ import org.mockito.kotlin.argThat as reifiedArgThat import android.content.ContentValues import android.database.Cursor import android.database.MatrixCursor +import android.database.SQLException +import android.database.sqlite.SQLiteDatabase import android.os.CancellationSignal import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery +import androidx.sqlite.db.SupportSQLiteStatement +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.ArgumentMatcher +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mockito.anyString import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.internal.verification.VerificationModeFactory.times +import org.mockito.invocation.InvocationOnMock import org.mockito.kotlin.anyArray import org.mockito.kotlin.eq +import org.mockito.kotlin.inOrder import org.mockito.kotlin.isNull +import org.mockito.stubbing.OngoingStubbing import java.util.stream.Stream import kotlin.streams.asStream @@ -149,7 +162,103 @@ class MappingSupportSQLiteDatabaseTest { verifyNoMoreInteractions(delegateMock) } - /* TODO insert */ + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestInsert") + fun testInsert(arguments: CallDataTwo) { + val (idCompiledStatement: SupportSQLiteStatement, idBindings: Map) = mockSupportSQLiteStatement() + val (commentCompiledStatement: SupportSQLiteStatement, commentBindings: Map) = mockSupportSQLiteStatement() + + whenCalled(delegateMock.compileStatement(arguments.idExpected)).thenReturn(idCompiledStatement) + whenCalled(delegateMock.compileStatement(arguments.commentExpected)).thenReturn(commentCompiledStatement) + + val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) + identityMapping.insert("id_test", 1, arguments.idCall) + + order.verify(delegateMock).compileStatement(arguments.idExpected) + order.verify(idCompiledStatement, times(arguments.idCall.argCount())).bindString(anyInt(), anyString()) + order.verify(idCompiledStatement, times(arguments.idCall.nullCount())).bindNull(anyInt()) + order.verify(idCompiledStatement, times(arguments.idCall.argCount())).bindLong(anyInt(), anyLong()) + order.verify(idCompiledStatement).executeInsert() + order.verify(idCompiledStatement).close() + verifyNoMoreInteractions(idCompiledStatement) + + order.verifyNoMoreInteractions() + commentMapping.insert("comment_test", 2, arguments.commentCall) + + order.verify(delegateMock).compileStatement(arguments.commentExpected) + /* */ verifyNoMoreInteractions(delegateMock) + order.verify(commentCompiledStatement, times(arguments.commentCall.argCount())).bindString(anyInt(), anyString()) + order.verify(commentCompiledStatement, times(arguments.commentCall.nullCount())).bindNull(anyInt()) + order.verify(commentCompiledStatement, times(arguments.commentCall.argCount())).bindLong(anyInt(), anyLong()) + order.verify(commentCompiledStatement).executeInsert() + order.verify(commentCompiledStatement).close() + verifyNoMoreInteractions(commentCompiledStatement) + + order.verifyNoMoreInteractions() + + assertEquals(arguments.idCall.toBindingsMap(), idBindings) + assertEquals(arguments.commentCall.toBindingsMap(), commentBindings) + } + + @Test + fun testInsertEmptyValues() { + val emptyContentValues = mockContentValues() + + assertThrows { identityMapping.insert("id_test", 1, emptyContentValues) } + assertThrows { commentMapping.insert("comment_test", 2, emptyContentValues) } + + verifyNoInteractions(delegateMock) + } + + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestInsertConflictAlgorithms") + fun testInsertConflictAlgorithms(arguments: Triple) { + val (conflictAlgorithm: Int, idStatement: String, commentStatement: String) = arguments + + val idCompiledStatement = mock(SupportSQLiteStatement::class.java) + val commentCompiledStatement = mock(SupportSQLiteStatement::class.java) + + whenCalled(delegateMock.compileStatement(idStatement)).thenReturn(idCompiledStatement) + whenCalled(delegateMock.compileStatement(commentStatement)).thenReturn(commentCompiledStatement) + + val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) + + val idContentValues = mockContentValues("col1" to "val1") //Inlining this declaration causes problems for some reason + assertDoesNotThrow { identityMapping.insert("id_test", conflictAlgorithm, idContentValues) } + + order.verify(delegateMock).compileStatement(idStatement) + order.verify(idCompiledStatement).bindString(1, "val1") + order.verify(idCompiledStatement).executeInsert() + order.verify(idCompiledStatement).close() + verifyNoMoreInteractions(idCompiledStatement) + + order.verifyNoMoreInteractions() + val commentContentValues = mockContentValues("col2" to "val2") + assertDoesNotThrow { commentMapping.insert("comment_test", conflictAlgorithm, commentContentValues) } + + order.verify(delegateMock).compileStatement(commentStatement) + /* */ verifyNoMoreInteractions(delegateMock) + order.verify(commentCompiledStatement).bindString(1, "val2") + order.verify(commentCompiledStatement).executeInsert() + order.verify(commentCompiledStatement).close() + verifyNoMoreInteractions(commentCompiledStatement) + + order.verifyNoMoreInteractions() + } + + @Test + fun testInsertInvalidConflictAlgorithms() { + val idContentValues = mockContentValues("col1" to "val1") //Inlining this declaration causes problems for some reason + val commentContentValues = mockContentValues("col2" to "val2") + + assertThrows { identityMapping.insert("id_test", -1, idContentValues) } + assertThrows { commentMapping.insert("comment_test", -1, commentContentValues) } + + assertThrows { identityMapping.insert("id_test", 6, idContentValues) } + assertThrows { commentMapping.insert("comment_test", 6, commentContentValues) } + + verifyNoInteractions(delegateMock) + } @ParameterizedTest @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestUpdate") @@ -241,6 +350,8 @@ private class PseudoEqualsMatcher( } } +private inline fun OngoingStubbing.thenDo(crossinline action: (invocation: InvocationOnMock) -> Unit): OngoingStubbing = thenAnswer { action(it) } + private class NullHandlingMatcher( private val delegate: ArgumentMatcher, private val matchNull: Boolean @@ -304,10 +415,34 @@ private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { return mock } +private fun mockSupportSQLiteStatement(): Pair> { + val bindings: MutableMap = mutableMapOf() + val mock = mock(SupportSQLiteStatement::class.java) + whenCalled(mock.bindString(anyInt(), anyString())).thenDo { + bindings[it.getArgument(0, Integer::class.java).toInt()] = it.getArgument(1, String::class.java) + } + whenCalled(mock.bindLong(anyInt(), anyLong())).thenDo { + bindings[it.getArgument(0, Integer::class.java).toInt()] = it.getArgument(1, java.lang.Long::class.java) + } + whenCalled(mock.bindNull(anyInt())).thenDo { + bindings[it.getArgument(0, Integer::class.java).toInt()] = null + } + return mock to bindings +} + private fun mockContentValues(vararg elements: Pair): ContentValues { - val entries = mapOf(*elements) + return mockContentValues(mapOf(*elements)) +} + +private fun mockContentValues(entries: Map): ContentValues { val mock = mock(ContentValues::class.java) whenCalled(mock.valueSet()).thenReturn(entries.entries) + whenCalled(mock.size()).thenReturn(entries.size) + whenCalled(mock.isEmpty).thenReturn(entries.isEmpty()) + whenCalled(mock.keySet()).thenReturn(entries.keys) + whenCalled(mock.get(anyString())).then { + entries[it.getArgument(0, String::class.java)] + } whenCalled(mock.toString()).thenReturn("Mock${entries}") return mock } @@ -319,6 +454,13 @@ data class CallData( val commentExpected: T ) +data class CallDataTwo( + val idCall: C, + val commentCall: C, + val idExpected: E, + val commentExpected: E +) + fun sourceForTestQueryCancelable(): Stream { val queries = sequenceOf( CallData( @@ -352,6 +494,87 @@ fun sourceForTestQueryCancelable(): Stream { return queries.cartesianProduct(signals).map { it.toList() }.toArgumentsStream() } +fun sourceForTestInsert(): Stream> = sequenceOf( + //The ContentValues in this dataset always have the following order and counts: + //String [0,2], null[0,1], Int[0,1] + //This makes the ordered verification a lot easier + CallDataTwo( + mockContentValues("key1" to null), + mockContentValues("key2" to null), + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1" to "value1"), + mockContentValues("key2" to "value2"), + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1-1" to "value1-1", "key1-2" to "value1-2"), + mockContentValues("key2-1" to "value2-1", "key2-2" to "value2-2"), + "INSERT OR ROLLBACK INTO id_test(key1-1,key1-2) VALUES (?,?)", + "INSERT OR ABORT INTO comment_test(key2-1,key2-2) VALUES (?,?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1" to "value1", "intKey1" to 10101), + mockContentValues("key2" to "value2", "intKey2" to 20202), + "INSERT OR ROLLBACK INTO id_test(key1,intKey1) VALUES (?,?)", + "INSERT OR ABORT INTO comment_test(key2,intKey2) VALUES (?,?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1" to "value1", "nullKey1" to null), + mockContentValues("key2" to "value2", "nullKey2" to null), + "INSERT OR ROLLBACK INTO id_test(key1,nullKey1) VALUES (?,?)", + "INSERT OR ABORT INTO comment_test(key2,nullKey2) VALUES (?,?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1" to "value1", "nullKey1" to null, "intKey1" to 10101), + mockContentValues("key2" to "value2"), + "INSERT OR ROLLBACK INTO id_test(key1,nullKey1,intKey1) VALUES (?,?,?)", + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" + ), + CallDataTwo( + mockContentValues("key1" to "value1"), + mockContentValues("key2" to "value2", "nullKey2" to null, "intKey2" to 20202), + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", + "INSERT OR ABORT INTO comment_test(key2,nullKey2,intKey2) VALUES (?,?,?) -- Comment!" + ) +).asStream() + +fun sourceForTestInsertConflictAlgorithms(): Stream> = sequenceOf( + Triple( + SQLiteDatabase.CONFLICT_NONE, + "INSERT INTO id_test(col1) VALUES (?)", + "INSERT INTO comment_test(col2) VALUES (?) -- Comment!" + ), + Triple( + SQLiteDatabase.CONFLICT_ROLLBACK, + "INSERT OR ROLLBACK INTO id_test(col1) VALUES (?)", + "INSERT OR ROLLBACK INTO comment_test(col2) VALUES (?) -- Comment!" + ), + Triple( + SQLiteDatabase.CONFLICT_ABORT, + "INSERT OR ABORT INTO id_test(col1) VALUES (?)", + "INSERT OR ABORT INTO comment_test(col2) VALUES (?) -- Comment!" + ), + Triple( + SQLiteDatabase.CONFLICT_FAIL, + "INSERT OR FAIL INTO id_test(col1) VALUES (?)", + "INSERT OR FAIL INTO comment_test(col2) VALUES (?) -- Comment!" + ), + Triple( + SQLiteDatabase.CONFLICT_IGNORE, + "INSERT OR IGNORE INTO id_test(col1) VALUES (?)", + "INSERT OR IGNORE INTO comment_test(col2) VALUES (?) -- Comment!" + ), + Triple( + SQLiteDatabase.CONFLICT_REPLACE, + "INSERT OR REPLACE INTO id_test(col1) VALUES (?)", + "INSERT OR REPLACE INTO comment_test(col2) VALUES (?) -- Comment!" + ), +).asStream() + fun sourceForTestUpdate(): Stream { val contentValues = sequenceOf( CallData( @@ -411,4 +634,16 @@ fun Sequence>.cartesianProduct(other: Iterable): Sequenc fun Sequence>.toArgumentsStream(): Stream = map { Arguments { it.toTypedArray() } -}.asStream() \ No newline at end of file +}.asStream() + + +private fun ContentValues.nullCount(): Int = valueSet().count { it.value == null } + +private inline fun ContentValues.argCount(): Int = valueSet().asSequence().map { it.value }.filterIsInstance().count() + +private fun ContentValues.toBindingsMap(): Map { + return valueSet().map { it.value } // + .map { if (it is Int) it.toLong() else it } // Required because java.lang.Integer.valueOf(x) != java.lang.Long.valueOf(x) + .mapIndexed { index, value -> index + 1 to value } // + .toMap() +} \ No newline at end of file From 6af9811758651fb85e2c46a6a7517a296d360b7f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:22:13 +0200 Subject: [PATCH 069/120] Refactored "Sql" to follow the API contract of "SupportSQLiteDatabase" This also makes the class compatible with "MappingSupportSQLiteDatabase" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../cryptomator/data/db/migrations/Sql.java | 28 +++++++++++++------ .../java/org/cryptomator/data/util/Utils.kt | 18 ++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/util/Utils.kt diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 24085cd33..4985ffef0 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -7,6 +7,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase; +import org.cryptomator.data.util.Utils; + import java.util.ArrayList; import java.util.List; @@ -121,6 +123,7 @@ public static class SqlQueryBuilder { private List columns = new ArrayList<>(); private String groupBy; private String having; + private String orderBy; private String limit; public SqlQueryBuilder(String tableName) { @@ -141,17 +144,22 @@ public SqlQueryBuilder where(String column, Criterion criterion) { } public SqlQueryBuilder groupBy(String groupBy) { - this.groupBy = groupBy; + this.groupBy = Utils.requireNullOrNotBlank(groupBy); return this; } public SqlQueryBuilder having(String having) { - this.having = having; + this.having = Utils.requireNullOrNotBlank(having); + return this; + } + + public SqlQueryBuilder orderBy(String orderBy) { + this.orderBy = Utils.requireNullOrNotBlank(orderBy); return this; } public SqlQueryBuilder limit(String limit) { - this.limit = limit; + this.limit = Utils.requireNullOrNotBlank(limit); return this; } @@ -162,11 +170,11 @@ public Cursor executeOn(SupportSQLiteDatabase db) { String query = SQLiteQueryBuilder.buildQueryString( // /* distinct */ false, // tableName, // - columns.toArray(new String[columns.size()]), // - whereClause.toString(), // + Utils.emptyToNull(columns.toArray(new String[columns.size()])), // + Utils.blankToNull(whereClause.toString()), // groupBy, // having, // - /* orderBy */ null, // + orderBy, // limit // ); //In contrast to "SupportSQLiteDatabase#update" "query" doesn't define how the contents of "whereArgs" are bound. @@ -210,7 +218,7 @@ public void executeOn(SupportSQLiteDatabase db) { //The internal binding methods are type-safe, but resolve to just putting all args into an "Array" in "SQLiteProgram" anyway. //This array is also used by "SQLiteDatabase#update". Apparently the contents of the array are then bound as "Strings". //As of now we always pass an "Array", but all of this has to be kept in mind if we ever change this. - db.update(tableName, SQLiteDatabase.CONFLICT_NONE, contentValues, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); + db.update(tableName, SQLiteDatabase.CONFLICT_NONE, contentValues, Utils.blankToNull(whereClause.toString()), whereArgs.toArray(new String[whereArgs.size()])); } } @@ -536,6 +544,10 @@ public SqlInsertBuilder integer(String column, Integer value) { } public Long executeOn(SupportSQLiteDatabase db) { + if (contentValues.size() == 0) { + throw new IllegalStateException("At least one value must be set"); + } + //In contrast to "SupportSQLiteDatabase#update" "insert" doesn't define how the contents of "contentValues" are bound. //As opposed to the other methods in this class, we do actually pass "Integers" and "Strings" here and again they appear //to end up in the "Array" in "SQLiteProgram". Currently there is no issue, @@ -566,7 +578,7 @@ public SqlDeleteBuilder where(String column, Criterion criterion) { public void executeOn(SupportSQLiteDatabase db) { //"SupportSQLiteDatabase#delete" always binds the contents of "whereArgs" as "Strings". //As of now we always pass an "Array", but this has to be kept in mind if we ever change this. See: "SqlUpdateBuilder#executeOn" - db.delete(tableName, whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()])); + db.delete(tableName, Utils.blankToNull(whereClause.toString()), whereArgs.toArray(new String[whereArgs.size()])); } } } diff --git a/data/src/main/java/org/cryptomator/data/util/Utils.kt b/data/src/main/java/org/cryptomator/data/util/Utils.kt new file mode 100644 index 000000000..88c8fa2ca --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/util/Utils.kt @@ -0,0 +1,18 @@ +@file:JvmName("Utils") + +package org.cryptomator.data.util + +fun String?.blankToNull(): String? { + return if (isNullOrBlank()) null else this +} + +fun Array?.emptyToNull(): Array? { + return if (isNullOrEmpty()) null else this +} + +fun String?.requireNullOrNotBlank(): String? { + if (this != null && this.isBlank()) { + throw IllegalArgumentException("String is blank") + } + return this +} \ No newline at end of file From abf19e28a60180238bf1cc5dd3275623822ed7ba Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 21 Apr 2024 17:50:14 +0200 Subject: [PATCH 070/120] Fixed "migrate12To14IndexSideEffects" This also makes the test method compatible with "MappingSupportSQLiteDatabase" Also added "assertIsUUID" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../cryptomator/data/db/CryptomatorAssert.java | 17 +++++++++++++++++ .../cryptomator/data/db/UpgradeDatabaseTest.kt | 8 ++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java index 2e6842883..1b3951a0f 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java +++ b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java @@ -5,11 +5,14 @@ import com.google.android.gms.common.util.Strings; import java.text.MessageFormat; +import java.util.regex.Pattern; import static org.junit.Assert.fail; public class CryptomatorAssert { + private final static Pattern UUID_PATTERN = Pattern.compile("^\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}$"); + private CryptomatorAssert() { } @@ -40,4 +43,18 @@ private static void failCursorNotEquals(String message, Cursor expected, Cursor CryptomatorDatabaseKt.stringify(actual)); fail(failMessage); } + + public static void assertIsUUID(String actual) { + assertIsUUID(null, actual); + } + + public static void assertIsUUID(String message, String actual) { + if (actual != null && UUID_PATTERN.matcher(actual).matches()) { + return; + } + String failMessage = MessageFormat.format("{0}: {1}", // + message != null && !Strings.isEmptyOrWhitespace(message) ? message : "String is not a valid UUID", // + actual != null ? '"' + actual + '"' : ""); + fail(failMessage); + } } \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 7d56029e6..51d1feb24 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -12,6 +12,7 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.google.common.base.Optional import org.cryptomator.data.db.CryptomatorAssert.assertCursorEquals +import org.cryptomator.data.db.CryptomatorAssert.assertIsUUID import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -43,6 +44,8 @@ import org.junit.runner.RunWith private const val TEST_DB = "migration-test" private const val LATEST_LEGACY_MIGRATION = 12 +private const val UUID_LENGTH = 36 + @RunWith(AndroidJUnit4::class) @SmallTest class UpgradeDatabaseTest { @@ -755,9 +758,10 @@ class UpgradeDatabaseTest { Upgrade11To12(sharedPreferencesHandler).migrate(db) val pre13Statement = indexStatement(db) - val pre13Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC)" + val pre13Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC) -- " //This is a sanity check and may need to be updated if Sql.java is changed - assertEquals(pre13Expected, pre13Statement) + assertEquals(pre13Expected, pre13Statement.substring(0, pre13Statement.length - UUID_LENGTH)) + assertIsUUID(pre13Statement.substring(pre13Statement.length - UUID_LENGTH)) db.close() helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> From 0e63cad0322516889b062ddbcaa0d6992eafd80f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:57:19 +0200 Subject: [PATCH 071/120] Applied fix for #529 [1] to re-enabled tests See: b3449f07510fc86203e6b3ded5cfe3ff72902b01 Also See: c13b12ffc8d441f344793f2436506974b145899f *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 51d1feb24..017097805 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -13,6 +13,7 @@ import androidx.test.platform.app.InstrumentationRegistry import com.google.common.base.Optional import org.cryptomator.data.db.CryptomatorAssert.assertCursorEquals import org.cryptomator.data.db.CryptomatorAssert.assertIsUUID +import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -93,7 +94,7 @@ class UpgradeDatabaseTest { assertEquals(1, oldVersion) assertEquals(LATEST_LEGACY_MIGRATION, newVersion) } - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + }).let { FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(it).writableDatabase } } @After From 9f70454a07d5ce93ec6b40f8ba12d2b4ffa499b8 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:31:07 +0200 Subject: [PATCH 072/120] Moved logic for handling extra calls to dedicated class "OneOffDelegate" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 13 +++---------- .../data/db/sqlmapping/OneOffDelegate.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/sqlmapping/OneOffDelegate.kt diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 1ba009fd2..5858f7e5d 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -164,18 +164,11 @@ internal class MappingSupportSQLiteDatabase( private val delegateQuery: SupportSQLiteQuery ) : SupportSQLiteQuery by delegateQuery { - private val lock = Any() - private var called = false - private val _sql = map(delegateQuery.sql) + private val sqlDelegate = OneOffDelegate { Timber.tag("MappingSupportSQLiteQuery").e("SQL queried twice") } + override val sql: String - get() = synchronized(lock) { - if (called) { - Timber.tag("MappingSupportSQLiteQuery").e("SQL queried twice") - } - called = true - return _sql - } + get() = sqlDelegate.call { _sql } } } diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/OneOffDelegate.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/OneOffDelegate.kt new file mode 100644 index 000000000..8d88ad43d --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/OneOffDelegate.kt @@ -0,0 +1,15 @@ +package org.cryptomator.data.db.sqlmapping + +internal class OneOffDelegate(private val beforeExtraCalls: () -> Unit) { + + private val lock = Any() + private var called = false + + fun call(delegateCallable: () -> R): R = synchronized(lock) { + if (called) { + beforeExtraCalls() + } + called = true + return delegateCallable() + } +} \ No newline at end of file From b691766aca1c4aa45834f472b3ea0fe18730cccc Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:32:26 +0200 Subject: [PATCH 073/120] Implemented "MappingSupportSQLiteQuery.bindTo" with "OneOffDelegate" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/sqlmapping/MappingSupportSQLiteDatabase.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 5858f7e5d..64e761149 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -7,6 +7,7 @@ import android.os.CancellationSignal import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement import timber.log.Timber @@ -166,9 +167,14 @@ internal class MappingSupportSQLiteDatabase( private val _sql = map(delegateQuery.sql) private val sqlDelegate = OneOffDelegate { Timber.tag("MappingSupportSQLiteQuery").e("SQL queried twice") } + private val bindToDelegate = OneOffDelegate { Timber.tag("MappingSupportSQLiteQuery").e("bindTo called twice") } override val sql: String get() = sqlDelegate.call { _sql } + + override fun bindTo(statement: SupportSQLiteProgram) { + bindToDelegate.call { delegateQuery.bindTo(statement) } + } } } From 77fa76f0fd52f7cf0af54f9581e39586d2bb373c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:56:09 +0200 Subject: [PATCH 074/120] Added "ValueExtractor" as argument type of "pseudo equality" methods Added "ValueExtractor" as type alias for "(T) -> Any?" Changed argument declarations of "pseudo equality" methods and underlying implementations Added "ValueExtractor" as type declaration for existing value extractor sets *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 91f18a229..e963ce72e 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -153,11 +153,11 @@ class MappingSupportSQLiteDatabaseTest { verify(delegateMock).query( anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), - anyPseudoEqualsUnlessNull(signals.idExpected, setOf(CancellationSignal::isCanceled)) + anyPseudoEqualsUnlessNull(signals.idExpected, setOf>(CancellationSignal::isCanceled)) ) verify(delegateMock).query( anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), - anyPseudoEqualsUnlessNull(signals.commentExpected, setOf(CancellationSignal::isCanceled)) + anyPseudoEqualsUnlessNull(signals.commentExpected, setOf>(CancellationSignal::isCanceled)) ) verifyNoMoreInteractions(delegateMock) } @@ -324,22 +324,22 @@ class MappingSupportSQLiteDatabaseTest { /* TODO compileStatement */ } -private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set<(T) -> Any?>): T? { +private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set>): T? { return if (other != null) defaultArgThat(NullHandlingMatcher(pseudoEquals(other, valueExtractors), false)) else isNull() } -private inline fun anyPseudoEquals(other: T, valueExtractors: Set<(T) -> Any?>): T { +private inline fun anyPseudoEquals(other: T, valueExtractors: Set>): T { return reifiedArgThat(pseudoEquals(other, valueExtractors)) } -private fun pseudoEquals(other: T, valueExtractors: Set<(T) -> Any?>): ArgumentMatcher { +private fun pseudoEquals(other: T, valueExtractors: Set>): ArgumentMatcher { require(valueExtractors.isNotEmpty()) return PseudoEqualsMatcher(other, valueExtractors) } private class PseudoEqualsMatcher( private val other: T, - private val valueExtractors: Set<(T) -> Any?> + private val valueExtractors: Set> ) : ArgumentMatcher { override fun matches(argument: T): Boolean { @@ -350,6 +350,8 @@ private class PseudoEqualsMatcher( } } +private typealias ValueExtractor = (T) -> Any? + private inline fun OngoingStubbing.thenDo(crossinline action: (invocation: InvocationOnMock) -> Unit): OngoingStubbing = thenAnswer { action(it) } private class NullHandlingMatcher( @@ -365,7 +367,7 @@ private class NullHandlingMatcher( } } -private val supportSQLiteQueryProperties +private val supportSQLiteQueryProperties: Set> get() = setOf(SupportSQLiteQuery::sql, SupportSQLiteQuery::argCount, { query: SupportSQLiteQuery -> CachingSupportSQLiteProgram().also { query.bindTo(it) }.bindings }) @@ -400,7 +402,7 @@ private class CachingSupportSQLiteProgram : SupportSQLiteProgram { override fun close() = throw UnsupportedOperationException("Stub!") } -private val contentValuesProperties +private val contentValuesProperties: Set> get() = setOf( ContentValues::valueSet ) From cfde324f58ceccee14a18650df95e6c9a7d12f7f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:57:39 +0200 Subject: [PATCH 075/120] Enforced single call convention for parts of "MappingSupportSQLiteQuery" Also added workaround for tests *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabase.kt | 5 ++--- .../MappingSupportSQLiteDatabaseTest.kt | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 64e761149..cc6e46a8a 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -10,7 +10,6 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement -import timber.log.Timber internal class MappingSupportSQLiteDatabase( private val delegate: SupportSQLiteDatabase, @@ -166,8 +165,8 @@ internal class MappingSupportSQLiteDatabase( ) : SupportSQLiteQuery by delegateQuery { private val _sql = map(delegateQuery.sql) - private val sqlDelegate = OneOffDelegate { Timber.tag("MappingSupportSQLiteQuery").e("SQL queried twice") } - private val bindToDelegate = OneOffDelegate { Timber.tag("MappingSupportSQLiteQuery").e("bindTo called twice") } + private val sqlDelegate = OneOffDelegate { throw IllegalStateException("SQL queried twice") } + private val bindToDelegate = OneOffDelegate { throw IllegalStateException("bindTo called twice") } override val sql: String get() = sqlDelegate.call { _sql } diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index e963ce72e..3c4548729 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -118,6 +118,7 @@ class MappingSupportSQLiteDatabaseTest { identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test`")) commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test`")) + val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), supportSQLiteQueryProperties) ) @@ -134,6 +135,7 @@ class MappingSupportSQLiteDatabaseTest { identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1"))) commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2"))) + val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), supportSQLiteQueryProperties) ) @@ -151,6 +153,7 @@ class MappingSupportSQLiteDatabaseTest { identityMapping.query(queries.idCall, signals.idCall) commentMapping.query(queries.commentCall, signals.commentCall) + val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), anyPseudoEqualsUnlessNull(signals.idExpected, setOf>(CancellationSignal::isCanceled)) @@ -352,6 +355,15 @@ private class PseudoEqualsMatcher( private typealias ValueExtractor = (T) -> Any? +private data class CacheEntry(val value: Any?) //Allows correct handling of nulls + +private fun ValueExtractor.asCached(): ValueExtractor { + val cache = mutableMapOf() + return { + cache.computeIfAbsent(it) { key -> CacheEntry(this@asCached(key)) }.value + } +} + private inline fun OngoingStubbing.thenDo(crossinline action: (invocation: InvocationOnMock) -> Unit): OngoingStubbing = thenAnswer { action(it) } private class NullHandlingMatcher( @@ -367,10 +379,13 @@ private class NullHandlingMatcher( } } -private val supportSQLiteQueryProperties: Set> - get() = setOf(SupportSQLiteQuery::sql, SupportSQLiteQuery::argCount, { query: SupportSQLiteQuery -> +private fun newCachedSupportSQLiteQueryProperties(): Set> = setOf( + SupportSQLiteQuery::sql.asCached(), + SupportSQLiteQuery::argCount, + { query: SupportSQLiteQuery -> CachingSupportSQLiteProgram().also { query.bindTo(it) }.bindings - }) + }.asCached() +) private class CachingSupportSQLiteProgram : SupportSQLiteProgram { From 40a4ed56796d9b58b98c284c425fb9015e34c665 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:56:09 +0200 Subject: [PATCH 076/120] Refactored "insert" and "compileStatement" Changed "insert" to directly compile the mapped sql instead of creating a "MappingSupportSQLiteStatement" Added check if the database is open to "compileStatement" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/sqlmapping/MappingSupportSQLiteDatabase.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index cc6e46a8a..f35f88776 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -48,10 +48,10 @@ internal class MappingSupportSQLiteDatabase( throw SQLiteException("Invalid conflict algorithm") } val processed = helper.insertWithOnConflict(table, null, values, conflictAlgorithm) - val statement = MappingSupportSQLiteStatement(processed.sql) + val statement = delegate.compileStatement(map(processed.sql)) SimpleSQLiteQuery.bind(statement, processed.bindArgs) - return statement.executeInsert() + return statement.use { it.executeInsert() } } override fun update( @@ -73,6 +73,9 @@ internal class MappingSupportSQLiteDatabase( } override fun compileStatement(sql: String): SupportSQLiteStatement { + if(!isOpen) { + throw SQLiteException("Database already closed") + } return MappingSupportSQLiteStatement(sql) } From bcfbef9ac120f148ff49dd1261dc0de2d4bf09b3 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 26 Apr 2024 22:17:09 +0200 Subject: [PATCH 077/120] Improved caching to properly handle unconventional keys *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 3c4548729..3ae72a841 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -355,12 +355,41 @@ private class PseudoEqualsMatcher( private typealias ValueExtractor = (T) -> Any? -private data class CacheEntry(val value: Any?) //Allows correct handling of nulls +private data class CacheKey(val wrappedKey: T) { + + override fun hashCode(): Int { + return if (isPrimitive(wrappedKey)) { + wrappedKey!!.hashCode() + } else { + System.identityHashCode(wrappedKey) + } + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is CacheKey<*>) { + return false + } + return if (isPrimitive(this.wrappedKey) && isPrimitive(other.wrappedKey)) { + this.wrappedKey == other.wrappedKey + } else { + this.wrappedKey === other.wrappedKey + } + } +} + +private data class CacheValue(val wrappedValue: Any?) //Allows correct handling of nulls + +private fun isPrimitive(obj: Any?): Boolean { + return when (obj) { + is Boolean, Char, Byte, Short, Int, Long, Float, Double -> true + else -> false + } +} private fun ValueExtractor.asCached(): ValueExtractor { - val cache = mutableMapOf() + val cache = mutableMapOf, CacheValue>() return { - cache.computeIfAbsent(it) { key -> CacheEntry(this@asCached(key)) }.value + cache.computeIfAbsent(CacheKey(it)) { key -> CacheValue(this@asCached(key.wrappedKey)) }.wrappedValue } } From 51056e6d708cce202f8a9c2d9c11dd77fedbc926 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:38:11 +0200 Subject: [PATCH 078/120] Added test for "SupportSQLiteDatabase.compileStatement" Also added missing "@VisibleForTesting" annotation to "MappingSupportSQLiteDatabase" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabase.kt | 6 +++- .../MappingSupportSQLiteDatabaseTest.kt | 32 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index f35f88776..ad644d7ac 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -10,7 +10,9 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement +import org.jetbrains.annotations.VisibleForTesting +@VisibleForTesting internal class MappingSupportSQLiteDatabase( private val delegate: SupportSQLiteDatabase, private val mappingFunction: SQLMappingFunction @@ -100,7 +102,9 @@ internal class MappingSupportSQLiteDatabase( return mappingFunction.mapWhereClause(whereClause) } - private inner class MappingSupportSQLiteStatement( + + @VisibleForTesting + internal inner class MappingSupportSQLiteStatement( private val sql: String ) : SupportSQLiteStatement { diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 3ae72a841..5b0d2ada9 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -16,7 +16,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement +import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertNotSame import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow @@ -324,7 +327,34 @@ class MappingSupportSQLiteDatabaseTest { verifyNoMoreInteractions(delegateMock) } - /* TODO compileStatement */ + @Test + fun testCompileStatement() { + whenCalled(delegateMock.isOpen).thenReturn(true) + + val idSql = "INSERT INTO `id_test` (`col1`) VALUES ('val1')" + val commentSql = "INSERT INTO `comment_test` (`col2`) VALUES ('val2')" + + val order = inOrder(delegateMock) + order.verifyNoMoreInteractions() + + val idStatement1 = identityMapping.compileStatement(idSql) + order.verify(delegateMock).isOpen + order.verifyNoMoreInteractions() + val idStatement2 = identityMapping.compileStatement(idSql) + val commentStatement1 = commentMapping.compileStatement(commentSql) + val commentStatement2 = commentMapping.compileStatement(commentSql) + + order.verify(delegateMock, times(3)).isOpen + order.verifyNoMoreInteractions() + + assertInstanceOf(MappingSupportSQLiteStatement::class.java, idStatement1) + assertInstanceOf(MappingSupportSQLiteStatement::class.java, idStatement2) + assertInstanceOf(MappingSupportSQLiteStatement::class.java, commentStatement1) + assertInstanceOf(MappingSupportSQLiteStatement::class.java, commentStatement2) + + assertNotSame(idStatement1, idStatement2) + assertNotSame(commentStatement1, commentStatement2) + } } private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set>): T? { From 5fef226c6edb08b808cbbf4e95d40145b9b29688 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 27 Apr 2024 23:20:37 +0200 Subject: [PATCH 079/120] Moved database templating to separate dagger module --- .../cryptomator/data/db/CreateDatabaseTest.kt | 4 +- .../data/db/UpgradeDatabaseTest.kt | 6 +- .../TemplateDatabaseContextTest.kt | 3 +- .../org/cryptomator/data/db/DatabaseModule.kt | 48 ++----------- .../data/db/templating/DbTemplateComponent.kt | 19 +++++ .../data/db/templating/DbTemplateModule.kt | 70 +++++++++++++++++++ .../data/db/templating/DbTemplateScoped.kt | 6 ++ 7 files changed, 109 insertions(+), 47 deletions(-) rename data/src/androidTest/java/org/cryptomator/data/db/{ => templating}/TemplateDatabaseContextTest.kt (92%) create mode 100644 data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt create mode 100644 data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt create mode 100644 data/src/main/java/org/cryptomator/data/db/templating/DbTemplateScoped.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index 980902a1c..31babc836 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -8,6 +8,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import org.cryptomator.data.db.migrations.Sql +import org.cryptomator.data.db.templating.DbTemplateModule +import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith @@ -21,7 +23,7 @@ class CreateDatabaseTest { @Test fun testProvideDbTemplateFile() { val templateDatabaseContext = TemplateDatabaseContext(context) - val templateFile = DatabaseModule().provideDbTemplateFile(templateDatabaseContext) + val templateFile = DbTemplateModule().let { it.provideDbTemplateFile(it.provideConfiguration(templateDatabaseContext)) } assertTrue(templateFile.exists()) val templateDb = SupportSQLiteOpenHelper.Configuration(templateDatabaseContext, DATABASE_NAME, object : SupportSQLiteOpenHelper.Callback(1) { diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 017097805..f0bf4cae4 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -27,6 +27,8 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.templating.DbTemplateModule +import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CredentialCryptor @@ -55,7 +57,9 @@ class UpgradeDatabaseTest { private val context = instrumentation.context private val sharedPreferencesHandler = SharedPreferencesHandler(context) - private val templateDbFile = DatabaseModule().provideDbTemplateFile(TemplateDatabaseContext(context)).also { + private val templateDbFile = DbTemplateModule().let { + it.provideDbTemplateFile(it.provideConfiguration(TemplateDatabaseContext(context))) + }.also { it.deleteOnExit() } diff --git a/data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt similarity index 92% rename from data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt rename to data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt index 4bc848196..101004727 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/TemplateDatabaseContextTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt @@ -1,8 +1,9 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.templating import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import org.cryptomator.data.db.DATABASE_NAME import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertSame diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 06a7bb2fd..9d25de724 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -1,15 +1,11 @@ package org.cryptomator.data.db import android.content.Context -import android.content.ContextWrapper import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import androidx.sqlite.db.SupportSQLiteOpenHelper -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled -import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 @@ -22,13 +18,12 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.templating.DbTemplateComponent import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named import java.io.File import java.io.InputStream -import java.nio.file.Files import java.util.concurrent.Callable -import javax.inject.Inject import javax.inject.Provider import javax.inject.Qualifier import javax.inject.Singleton @@ -39,7 +34,7 @@ import timber.log.Timber private val LOG = Timber.Forest.named("DatabaseModule") -@Module +@Module(subcomponents = [DbTemplateComponent::class]) class DatabaseModule { @Singleton @@ -76,30 +71,8 @@ class DatabaseModule { @Singleton @Provides @DbInternal - fun provideDbTemplateFile(templateDatabaseContext: TemplateDatabaseContext): File { - LOG.d("Creating database template file") - ThreadUtil.assumeNotMainThread() - val db = SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // - .name(DATABASE_NAME) // - .callback(object : SupportSQLiteOpenHelper.Callback(1) { - override fun onConfigure(db: SupportSQLiteDatabase) { - db.disableWriteAheadLogging() - db.setForeignKeyConstraintsEnabled(true) - } - - override fun onCreate(db: SupportSQLiteDatabase) { - Upgrade0To1().migrate(db) - } - - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - throw IllegalStateException("Template may not be upgraded") - } - }).build().let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } - require(db.version == 1) - db.close() - - LOG.d("Created database template file") - return File(requireNotNull(db.path)) + fun provideDbTemplateFile(templateFactory: DbTemplateComponent.Factory): File { + return templateFactory.create().templateFile() } @Singleton @@ -154,19 +127,6 @@ class DatabaseModule { ) } -@Singleton -class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWrapper(context) { - - private val dbFile: File by lazy { - return@lazy Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() - } - - override fun getDatabasePath(name: String?): File { - require(name == DATABASE_NAME) - return dbFile - } -} - object DatabaseCallback : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt new file mode 100644 index 000000000..96f341e34 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt @@ -0,0 +1,19 @@ +package org.cryptomator.data.db.templating + +import java.io.File +import dagger.Subcomponent + +@DbTemplateScoped +@Subcomponent(modules = [DbTemplateModule::class]) +interface DbTemplateComponent { + + @DbTemplateScoped + fun templateFile(): File + + @Subcomponent.Factory + interface Factory { + + fun create(): DbTemplateComponent + + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt new file mode 100644 index 000000000..fbb85e7af --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt @@ -0,0 +1,70 @@ +package org.cryptomator.data.db.templating + +import android.content.Context +import android.content.ContextWrapper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.data.db.DATABASE_NAME +import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 +import org.cryptomator.util.ThreadUtil +import org.cryptomator.util.named +import java.io.File +import java.nio.file.Files +import javax.inject.Inject +import dagger.Module +import dagger.Provides +import timber.log.Timber + +private val LOG = Timber.Forest.named("DbTemplateModule") + +@Module +class DbTemplateModule { + + @DbTemplateScoped + @Provides + internal fun provideDbTemplateFile(configuration: SupportSQLiteOpenHelper.Configuration): File { + LOG.d("Creating database template file") + ThreadUtil.assumeNotMainThread() + val db = configuration.let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + require(db.version == 1) + db.close() + + LOG.d("Created database template file") + return File(requireNotNull(db.path)) + } + + @DbTemplateScoped + @Provides + internal fun provideConfiguration(templateDatabaseContext: TemplateDatabaseContext): SupportSQLiteOpenHelper.Configuration { + return SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // + .name(DATABASE_NAME) // + .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onConfigure(db: SupportSQLiteDatabase) { + db.disableWriteAheadLogging() + db.setForeignKeyConstraintsEnabled(true) + } + + override fun onCreate(db: SupportSQLiteDatabase) { + Upgrade0To1().migrate(db) + } + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + throw IllegalStateException("Template may not be upgraded") + } + }).build() + } +} + +@DbTemplateScoped +internal class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWrapper(context) { + + private val dbFile: File by lazy { + return@lazy Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() + } + + override fun getDatabasePath(name: String?): File { + require(name == DATABASE_NAME) + return dbFile + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateScoped.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateScoped.kt new file mode 100644 index 000000000..1cfefdb86 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateScoped.kt @@ -0,0 +1,6 @@ +package org.cryptomator.data.db.templating; + +import javax.inject.Scope; + +@Scope +annotation class DbTemplateScoped \ No newline at end of file From a21681389fb511acc66ba2a4ade548af19f10dc6 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 14 May 2024 17:10:57 +0200 Subject: [PATCH 080/120] Made database templating more robust Added additional validation Moved database operations to "use" blocks --- .../data/db/templating/DbTemplateModule.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt index fbb85e7af..3086751aa 100644 --- a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt @@ -26,12 +26,21 @@ class DbTemplateModule { internal fun provideDbTemplateFile(configuration: SupportSQLiteOpenHelper.Configuration): File { LOG.d("Creating database template file") ThreadUtil.assumeNotMainThread() - val db = configuration.let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } - require(db.version == 1) - db.close() + return FrameworkSQLiteOpenHelperFactory().create(configuration).use { + initDatabase(it) + }.let { + require(it != null && it != ":memory:") { "Template database must not be in-memory" } + LOG.d("Created database template file") + File(it) + } + } - LOG.d("Created database template file") - return File(requireNotNull(db.path)) + private fun initDatabase(openHelper: SupportSQLiteOpenHelper): String? { + return openHelper.writableDatabase.use { + require(it.version == 1) + require(it.compileStatement("SELECT COUNT(*) FROM `CLOUD_ENTITY`").simpleQueryForLong() == 4L) + it.path + } } @DbTemplateScoped From 499afed02e81081f1025221f29d6e9a17f8096f4 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 7 May 2024 22:57:36 +0200 Subject: [PATCH 081/120] Added handling for database corruption If database corruption is detected the database is deleted and then recreated. Properly recreating and opening the new database involves invalidating any references to the old database and then running any initialization logic. Added "Invalidatable" as wrapper for database references Added "DatabaseOpenHelperFactory" to the dagger graph and added invalidation logic to "PatchedCallback" Added dynamically delegating implementations for all entities to allow seamless swapping of underlying database instances Configured Room to initialize based on the database template if a database with version 0 is encountered --- .../data/db/UpgradeDatabaseTest.kt | 2 +- .../java/org/cryptomator/data/db/CloudDao.kt | 18 +++++++++ .../org/cryptomator/data/db/DatabaseModule.kt | 38 ++++++++++++++----- .../data/db/DatabaseOpenHelperFactory.kt | 23 ++++++++--- .../org/cryptomator/data/db/Invalidatable.kt | 36 ++++++++++++++++++ .../org/cryptomator/data/db/UpdateCheckDao.kt | 18 +++++++++ .../java/org/cryptomator/data/db/VaultDao.kt | 18 +++++++++ .../data/db/mappers/VaultEntityMapper.java | 7 ++-- .../data/repository/CloudRepositoryImpl.java | 13 +++---- .../repository/UpdateCheckRepositoryImpl.java | 17 ++++----- .../data/repository/VaultRepositoryImpl.java | 13 +++---- 11 files changed, 161 insertions(+), 42 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/Invalidatable.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index f0bf4cae4..a01382df8 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -70,7 +70,7 @@ class UpgradeDatabaseTest { instrumentation, // CryptomatorDatabase::class.java, // listOf(), //TODO AutoSpecs - DatabaseOpenHelperFactory() + DatabaseOpenHelperFactory { throw IllegalStateException() } ) @Before diff --git a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt index 905a39eb6..4c14e4db5 100644 --- a/data/src/main/java/org/cryptomator/data/db/CloudDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/CloudDao.kt @@ -30,4 +30,22 @@ interface CloudDao { @Delete fun delete(entity: CloudEntity) +} + +internal class DelegatingCloudDao(private val database: Invalidatable) : CloudDao { + + private val delegate: CloudDao + get() = database.call().cloudDao() + + override fun load(id: Long): CloudEntity = delegate.load(id) + + override fun loadAll(): List = delegate.loadAll() + + override fun storeReplacing(entity: CloudEntity): RowId = delegate.storeReplacing(entity) + + override fun loadFromRowId(rowId: RowId): CloudEntity = delegate.loadFromRowId(rowId) + + override fun storeReplacingAndReload(entity: CloudEntity): CloudEntity = delegate.storeReplacingAndReload(entity) + + override fun delete(entity: CloudEntity) = delegate.delete(entity) } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 9d25de724..358040f4e 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -24,6 +24,7 @@ import org.cryptomator.util.named import java.io.File import java.io.InputStream import java.util.concurrent.Callable +import javax.inject.Named import javax.inject.Provider import javax.inject.Qualifier import javax.inject.Singleton @@ -39,15 +40,34 @@ class DatabaseModule { @Singleton @Provides - fun provideCryptomatorDatabase(context: Context, @DbInternal migrations: Array, @DbInternal dbTemplateStreamCallable: Callable): CryptomatorDatabase { + fun provideCryptomatorDatabase(@DbInternal delegate: Provider): Invalidatable = Invalidatable { + delegate.get() + } + + @Singleton + @Provides + @Named("databaseInvalidationCallback") + fun provideInvalidationCallback(invalidatable: Invalidatable): Function0 { + return invalidatable::invalidate + } + + @Provides + @DbInternal + internal fun provideInternalCryptomatorDatabase( + context: Context, // + @DbInternal migrations: Array, // + @DbInternal dbTemplateStreamCallable: Callable, // + openHelperFactory: DatabaseOpenHelperFactory, // + ): CryptomatorDatabase { LOG.i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) ThreadUtil.assumeNotMainThread() return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(DatabaseOpenHelperFactory().asCacheControlled()) // + .openHelperFactory(openHelperFactory.asCacheControlled()) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // + .fallbackToDestructiveMigrationFrom(0) // .build() //Fails if no migration is found (especially when downgrading) .also { // //Migrations are only triggered once the database is used for the first time. @@ -77,20 +97,20 @@ class DatabaseModule { @Singleton @Provides - fun provideCloudDao(database: Provider): CloudDao { - return database.get().cloudDao() + fun provideCloudDao(database: Invalidatable): CloudDao { + return DelegatingCloudDao(database) } @Singleton @Provides - fun provideUpdateCheckDao(database: Provider): UpdateCheckDao { - return database.get().updateCheckDao() + fun provideUpdateCheckDao(database: Invalidatable): UpdateCheckDao { + return DelegatingUpdateCheckDao(database) } @Singleton @Provides - fun provideVaultDao(database: Provider): VaultDao { - return database.get().vaultDao() + fun provideVaultDao(database: Invalidatable): VaultDao { + return DelegatingVaultDao(database) } @Singleton @@ -130,7 +150,7 @@ class DatabaseModule { object DatabaseCallback : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { - //This should not be called + //This should not be called except if there was corruption and the recovery in CopyOpenHelper failed; in that case PatchedCallback will invalidate the db throw UnsupportedOperationException("Creation is handled as upgrade") } diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index 11d11b68b..bb89462f1 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -4,32 +4,42 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import org.cryptomator.util.named +import java.io.File +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton import timber.log.Timber private val LOG = Timber.Forest.named("DatabaseOpenHelperFactory") //This needs to stay in sync with UpgradeDatabaseTest#setup +@Singleton internal class DatabaseOpenHelperFactory( - private val delegate: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory() + private val invalidationCallback: Function0, // + private val delegate: SupportSQLiteOpenHelper.Factory ) : SupportSQLiteOpenHelper.Factory { + @Inject + constructor(@Named("databaseInvalidationCallback") invalidationCallback: Function0) : this(invalidationCallback, FrameworkSQLiteOpenHelperFactory()) + override fun create(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper { LOG.d("Creating SupportSQLiteOpenHelper for database \"${configuration.name}\"") - return delegate.create(patchConfiguration(configuration)) + return delegate.create(patchConfiguration(invalidationCallback, configuration)) } } -private fun patchConfiguration(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper.Configuration { +private fun patchConfiguration(invalidationCallback: Function0, configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper.Configuration { return SupportSQLiteOpenHelper.Configuration( context = configuration.context, name = configuration.name, - callback = PatchedCallback(configuration.callback), + callback = PatchedCallback(invalidationCallback, configuration.callback), useNoBackupDirectory = configuration.useNoBackupDirectory, allowDataLossOnRecovery = configuration.allowDataLossOnRecovery ) } private class PatchedCallback( + private val invalidationCallback: Function0, private val delegateCallback: SupportSQLiteOpenHelper.Callback, ) : SupportSQLiteOpenHelper.Callback(delegateCallback.version) { @@ -42,9 +52,11 @@ private class PatchedCallback( } override fun onCreate(db: SupportSQLiteDatabase) { + //This should not be called except if there was corruption and the recovery in CopyOpenHelper failed; in that case invalidate the db LOG.e(Exception(), "Called onCreate for \"${db.path}\"@${db.version}") + invalidationCallback.invoke() // - delegateCallback.onCreate(db) + delegateCallback.onCreate(db) //Callback from DatabaseModule will throw here // } @@ -66,6 +78,7 @@ private class PatchedCallback( // delegateCallback.onCorruption(db) // + invalidationCallback.invoke() } override fun onOpen(db: SupportSQLiteDatabase) { diff --git a/data/src/main/java/org/cryptomator/data/db/Invalidatable.kt b/data/src/main/java/org/cryptomator/data/db/Invalidatable.kt new file mode 100644 index 000000000..d218b8266 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/Invalidatable.kt @@ -0,0 +1,36 @@ +package org.cryptomator.data.db + +import java.util.concurrent.Callable + +/** + * Instances of this class are thread-safe [Callables][Callable] that cache their results after the first [call,][call] similar to [Lazy.][Lazy] + * However, they allow the stored result to be invalidated with [invalidate,][invalidate] after which the result is recalculated the next time [call] is invoked. + */ +class Invalidatable(private val delegate: Callable) : Callable { + + @Volatile + private var instance: Any? = UNINITIALIZED + + override fun call(): T { + synchronized(this) { + if (instance === UNINITIALIZED) { + instance = delegate.call() + } + @Suppress("UNCHECKED_CAST") // + return instance as T + } + } + + fun invalidate() { + synchronized(this) { + instance = UNINITIALIZED + } + } + + companion object { + + private val UNINITIALIZED = object { + override fun toString(): String = "UNINITIALIZED" + } + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt index 0d28df3a8..e95fbd3eb 100644 --- a/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/UpdateCheckDao.kt @@ -31,4 +31,22 @@ interface UpdateCheckDao { @Delete fun delete(entity: UpdateCheckEntity) +} + +internal class DelegatingUpdateCheckDao(private val database: Invalidatable) : UpdateCheckDao { + + private val delegate: UpdateCheckDao + get() = database.call().updateCheckDao() + + override fun load(id: Long): UpdateCheckEntity = delegate.load(id) + + override fun loadAll(): List = delegate.loadAll() + + override fun storeReplacing(entity: UpdateCheckEntity): RowId = delegate.storeReplacing(entity) + + override fun loadFromRowId(rowId: RowId): UpdateCheckEntity = delegate.loadFromRowId(rowId) + + override fun storeReplacingAndReload(entity: UpdateCheckEntity): UpdateCheckEntity = delegate.storeReplacingAndReload(entity) + + override fun delete(entity: UpdateCheckEntity) = delegate.delete(entity) } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/VaultDao.kt b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt index 229dc8b87..f0061f7d1 100644 --- a/data/src/main/java/org/cryptomator/data/db/VaultDao.kt +++ b/data/src/main/java/org/cryptomator/data/db/VaultDao.kt @@ -30,4 +30,22 @@ interface VaultDao { @Delete fun delete(entity: VaultEntity) +} + +internal class DelegatingVaultDao(private val database: Invalidatable) : VaultDao { + + private val delegate: VaultDao + get() = database.call().vaultDao() + + override fun load(id: Long): VaultEntity = delegate.load(id) + + override fun loadAll(): List = delegate.loadAll() + + override fun storeReplacing(entity: VaultEntity): RowId = delegate.storeReplacing(entity) + + override fun loadFromRowId(rowId: RowId): VaultEntity = delegate.loadFromRowId(rowId) + + override fun storeReplacingAndReload(entity: VaultEntity): VaultEntity = delegate.storeReplacingAndReload(entity) + + override fun delete(entity: VaultEntity) = delegate.delete(entity) } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java index 0012d4aba..59707ba69 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java @@ -8,7 +8,6 @@ import org.cryptomator.domain.exception.BackendException; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import static org.cryptomator.domain.Vault.aVault; @@ -17,10 +16,10 @@ public class VaultEntityMapper extends EntityMapper { private final CloudEntityMapper cloudEntityMapper; - private final Provider cloudDao; + private final CloudDao cloudDao; @Inject - public VaultEntityMapper(CloudEntityMapper cloudEntityMapper, Provider cloudDao) { + public VaultEntityMapper(CloudEntityMapper cloudEntityMapper, CloudDao cloudDao) { this.cloudDao = cloudDao; this.cloudEntityMapper = cloudEntityMapper; } @@ -44,7 +43,7 @@ private Cloud cloudFrom(VaultEntity entity) { if (entity.getFolderCloudId() == null) { return null; } - return cloudEntityMapper.fromEntity(cloudDao.get().load(entity.getFolderCloudId())); + return cloudEntityMapper.fromEntity(cloudDao.load(entity.getFolderCloudId())); } @Override diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index 3e06f3249..ad1233721 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -19,13 +19,12 @@ import java.util.List; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; @Singleton class CloudRepositoryImpl implements CloudRepository { - private final Provider cloudDao; + private final CloudDao cloudDao; private final CryptoCloudFactory cryptoCloudFactory; private final CloudEntityMapper mapper; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -33,7 +32,7 @@ class CloudRepositoryImpl implements CloudRepository { @Inject public CloudRepositoryImpl(CloudEntityMapper mapper, // CryptoCloudFactory cryptoCloudFactory, // - Provider cloudDao, // + CloudDao cloudDao, // DispatchingCloudContentRepository dispatchingCloudContentRepository) { this.cloudDao = cloudDao; this.cryptoCloudFactory = cryptoCloudFactory; @@ -44,7 +43,7 @@ public CloudRepositoryImpl(CloudEntityMapper mapper, // @Override public List clouds(CloudType cloudType) throws BackendException { List cloudsFromType = new ArrayList<>(); - List allClouds = mapper.fromEntities(cloudDao.get().loadAll()); + List allClouds = mapper.fromEntities(cloudDao.loadAll()); for (Cloud cloud : allClouds) { if (cloud.type().equals(cloudType)) { @@ -57,7 +56,7 @@ public List clouds(CloudType cloudType) throws BackendException { @Override public List allClouds() throws BackendException { - return mapper.fromEntities(cloudDao.get().loadAll()); + return mapper.fromEntities(cloudDao.loadAll()); } @Override @@ -66,7 +65,7 @@ public Cloud store(Cloud cloud) { throw new IllegalArgumentException("Can not store non persistent cloud"); } - Cloud storedCloud = mapper.fromEntity(cloudDao.get().storeReplacingAndReload(mapper.toEntity(cloud))); + Cloud storedCloud = mapper.fromEntity(cloudDao.storeReplacingAndReload(mapper.toEntity(cloud))); dispatchingCloudContentRepository.updateCloudContentRepositoryFor(storedCloud); @@ -78,7 +77,7 @@ public void delete(Cloud cloud) { if (!cloud.persistent()) { throw new IllegalArgumentException("Can not delete non persistent cloud"); } - cloudDao.get().delete(mapper.toEntity(cloud)); + cloudDao.delete(mapper.toEntity(cloud)); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cloud); } diff --git a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java index 124043dc2..e2f046793 100644 --- a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java @@ -34,7 +34,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import okhttp3.OkHttpClient; @@ -49,12 +48,12 @@ public class UpdateCheckRepositoryImpl implements UpdateCheckRepository { private static final String HOSTNAME_LATEST_VERSION = "https://api.cryptomator.org/android/latest-version.json"; - private final Provider updateCheckDao; + private final UpdateCheckDao updateCheckDao; private final OkHttpClient httpClient; private final Context context; @Inject - UpdateCheckRepositoryImpl(Provider updateCheckDao, Context context) { + UpdateCheckRepositoryImpl(UpdateCheckDao updateCheckDao, Context context) { this.httpClient = httpClient(); this.updateCheckDao = updateCheckDao; this.context = context; @@ -74,7 +73,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back return Optional.absent(); } - final UpdateCheckEntity entity = updateCheckDao.get().load(1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); if (entity.getVersion() != null && entity.getVersion().equals(latestVersion.version) && entity.getApkSha256() != null) { return Optional.of(new UpdateCheckImpl("", entity)); @@ -85,7 +84,7 @@ public Optional getUpdateCheck(final String appVersion) throws Back entity.setVersion(updateCheck.getVersion()); entity.setApkSha256(updateCheck.getApkSha256()); - updateCheckDao.get().storeReplacing(entity); + updateCheckDao.storeReplacing(entity); return Optional.of(updateCheck); } @@ -93,22 +92,22 @@ public Optional getUpdateCheck(final String appVersion) throws Back @Nullable @Override public String getLicense() { - return updateCheckDao.get().load(1L).getLicenseToken(); + return updateCheckDao.load(1L).getLicenseToken(); } @Override public void setLicense(String license) { - final UpdateCheckEntity entity = updateCheckDao.get().load(1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); entity.setLicenseToken(license); - updateCheckDao.get().storeReplacing(entity); + updateCheckDao.storeReplacing(entity); } @Override public void update(File file) throws GeneralUpdateErrorException { try { - final UpdateCheckEntity entity = updateCheckDao.get().load(1L); + final UpdateCheckEntity entity = updateCheckDao.load(1L); final Request request = new Request // .Builder() // diff --git a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java index e6fd59760..bc6d23a45 100644 --- a/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/VaultRepositoryImpl.java @@ -15,7 +15,6 @@ import java.util.List; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import static org.cryptomator.domain.Vault.aCopyOf; @@ -23,7 +22,7 @@ @Singleton class VaultRepositoryImpl implements VaultRepository { - private final Provider vaultDao; + private final VaultDao vaultDao; private final VaultEntityMapper mapper; private final CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory; private final DispatchingCloudContentRepository dispatchingCloudContentRepository; @@ -35,7 +34,7 @@ public VaultRepositoryImpl( // CryptoCloudContentRepositoryFactory cryptoCloudContentRepositoryFactory, // CryptoCloudFactory cryptoCloudFactory, // DispatchingCloudContentRepository dispatchingCloudContentRepository, // - Provider vaultDao) { + VaultDao vaultDao) { this.mapper = mapper; this.vaultDao = vaultDao; this.cryptoCloudContentRepositoryFactory = cryptoCloudContentRepositoryFactory; @@ -46,7 +45,7 @@ public VaultRepositoryImpl( // @Override public List vaults() throws BackendException { List result = new ArrayList<>(); - for (Vault vault : mapper.fromEntities(vaultDao.get().loadAll())) { + for (Vault vault : mapper.fromEntities(vaultDao.loadAll())) { result.add(aCopyOf(vault).withUnlocked(isUnlocked(vault)).build()); } return result; @@ -55,7 +54,7 @@ public List vaults() throws BackendException { @Override public Vault store(Vault vault) throws BackendException { try { - return mapper.fromEntity(vaultDao.get().storeReplacingAndReload(mapper.toEntity(vault))); + return mapper.fromEntity(vaultDao.storeReplacingAndReload(mapper.toEntity(vault))); } catch (SQLiteConstraintException e) { throw new VaultAlreadyExistException(); } @@ -65,13 +64,13 @@ public Vault store(Vault vault) throws BackendException { public Long delete(Vault vault) throws BackendException { deregisterUnlocked(vault); dispatchingCloudContentRepository.removeCloudContentRepositoryFor(cryptoCloudFactory.decryptedViewOf(vault)); - vaultDao.get().delete(mapper.toEntity(vault)); + vaultDao.delete(mapper.toEntity(vault)); return vault.getId(); } @Override public Vault load(Long id) throws BackendException { - Vault vault = mapper.fromEntity(vaultDao.get().load(id)); + Vault vault = mapper.fromEntity(vaultDao.load(id)); return aCopyOf(vault).withUnlocked(isUnlocked(vault)).build(); } From fed62e2997982124dee044f318040abf42449c7a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 14 May 2024 19:11:11 +0200 Subject: [PATCH 082/120] Added "useFinally" to improve corruption handling --- .../data/db/DatabaseOpenHelperFactory.kt | 6 +++-- .../java/org/cryptomator/data/util/Utils.kt | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index bb89462f1..e40271785 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -3,6 +3,7 @@ package org.cryptomator.data.db import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.data.util.useFinally import org.cryptomator.util.named import java.io.File import javax.inject.Inject @@ -74,12 +75,13 @@ private class PatchedCallback( // } - override fun onCorruption(db: SupportSQLiteDatabase) { + override fun onCorruption(db: SupportSQLiteDatabase) = useFinally({ // delegateCallback.onCorruption(db) // + }, finallyBlock = { invalidationCallback.invoke() - } + }) override fun onOpen(db: SupportSQLiteDatabase) { // diff --git a/data/src/main/java/org/cryptomator/data/util/Utils.kt b/data/src/main/java/org/cryptomator/data/util/Utils.kt index 88c8fa2ca..ed6e20705 100644 --- a/data/src/main/java/org/cryptomator/data/util/Utils.kt +++ b/data/src/main/java/org/cryptomator/data/util/Utils.kt @@ -2,6 +2,8 @@ package org.cryptomator.data.util +import java.io.Closeable + fun String?.blankToNull(): String? { return if (isNullOrBlank()) null else this } @@ -15,4 +17,28 @@ fun String?.requireNullOrNotBlank(): String? { throw IllegalArgumentException("String is blank") } return this +} + +/** + * Executes the given [tryBlock] function on this object and then always executes the [finallyBlock] + * whether an exception is thrown or not, similar to a regular `finally` block. + * + * When using a regular `finally` block, an exception thrown by it will cause any exceptions + * thrown by the `try` block to be discarded. + * In contrast, [use][kotlin.io.use] and this method ensure that exceptions thrown by the [finallyBlock] do not suppress + * exceptions thrown by the [tryBlock.][tryBlock] If both the [tryBlock] and the [finallyBlock] throw exceptions, + * the exception from the [finallyBlock] is added to the list of suppressed exceptions of the exception + * thrown by the [tryBlock.][tryBlock] + * + * This method effectively turns any object into a [Closeable,][Closeable] where the [finallyBlock] is used as the implementation of the [Closeable.close] method + * and the [tryBlock] is executed on it with [use.][kotlin.io.use] + * + * @see kotlin.io.use + */ +inline fun T.useFinally(tryBlock: (T) -> R, crossinline finallyBlock: (T) -> Unit): R { + return Closeable { + finallyBlock(this) + }.use { + tryBlock(this) + } } \ No newline at end of file From b00ec8e1398abf680faa63c03edd471b71129f2a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 30 May 2024 00:12:04 +0200 Subject: [PATCH 083/120] Added test for opening previously corrupted/version 0 database Also added "databaseName" to parameter list of "provideInternalCryptomatorDatabase" to allow different names for test databases --- .../data/db/CorruptedDatabaseTest.kt | 129 ++++++++++++++++++ .../org/cryptomator/data/db/DatabaseModule.kt | 8 +- 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt new file mode 100644 index 000000000..27edac899 --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -0,0 +1,129 @@ +package org.cryptomator.data.db + +import android.content.Context +import androidx.room.migration.Migration +import androidx.room.util.readVersion +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 +import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 +import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 +import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 +import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 +import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 +import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 +import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 +import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 +import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 +import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.templating.DbTemplateModule +import org.cryptomator.data.db.templating.TemplateDatabaseContext +import org.cryptomator.data.util.useFinally +import org.cryptomator.util.SharedPreferencesHandler +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_DB = "corruption-test" + +@RunWith(AndroidJUnit4::class) +@SmallTest +class CorruptedDatabaseTest { + + private val context = InstrumentationRegistry.getInstrumentation().context + private val sharedPreferencesHandler = SharedPreferencesHandler(context) + private val openHelperFactory = DatabaseOpenHelperFactory { throw IllegalStateException() } + private val templateDbFile = DbTemplateModule().let { + it.provideDbTemplateFile(it.provideConfiguration(TemplateDatabaseContext(context))) + }.also { + it.deleteOnExit() + } + + @Before + fun setup() { + context.getDatabasePath(TEST_DB).also { dbFile -> + if (dbFile.exists()) { + //This may happen when killing the process while using the debugger + println("Test database \"${dbFile.absolutePath}\" not cleaned up. Deleting...") + dbFile.delete() + } + } + } + + @After + fun tearDown() { + context.getDatabasePath(TEST_DB).delete() + } + + @Test + fun testOpenVersion0Database() { + val databaseModule = DatabaseModule() + val migrations = arrayOf( + Upgrade1To2(), + Upgrade2To3(context), + Upgrade3To4(), + Upgrade4To5(), + Upgrade5To6(), + Upgrade6To7(), + Upgrade7To8(), + Upgrade8To9(sharedPreferencesHandler), + Upgrade9To10(sharedPreferencesHandler), + Upgrade10To11(), + Upgrade11To12(sharedPreferencesHandler), + Migration12To13() + ) + + createVersion0Database(context, TEST_DB) + databaseModule.provideInternalCryptomatorDatabase( + context, + migrations, + databaseModule.provideDbTemplateStreamCallable { templateDbFile }, + openHelperFactory, + TEST_DB + ).useFinally({ db -> + db.compileStatement("SELECT count(*) FROM `sqlite_master` WHERE `name` = 'CLOUD_ENTITY'").use { statement -> + require(statement.simpleQueryForLong() == 1L) + } + }, finallyBlock = CryptomatorDatabase::close) + } +} + +@Suppress("serial") +private class InterruptCreationException : Exception() + +private fun createVersion0Database(context: Context, databaseName: String) { + val config = SupportSQLiteOpenHelper.Configuration.builder(context) // + .name(databaseName) // + .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onConfigure(db: SupportSQLiteDatabase) { + db.disableWriteAheadLogging() + db.setForeignKeyConstraintsEnabled(true) + } + + override fun onCreate(db: SupportSQLiteDatabase) = throw InterruptCreationException() + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = throw IllegalStateException() + }).build() + + FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> + try { + //The "use" block in "initVersion0Database" should not be reached, let alone finished; ... + initVersion0Database(openHelper) + } catch (e: InterruptCreationException) { + //... instead, the creation of the database should be interrupted by the InterruptCreationException thrown by "onCreate", + //so that this catch block is called and the database remains in version 0. + require(readVersion(context.getDatabasePath(databaseName)) == 0) + } + } +} + +private fun initVersion0Database(openHelper: SupportSQLiteOpenHelper): Nothing { + openHelper.writableDatabase.use { + throw IllegalStateException("Creating a v0 database requires throwing an exception during creation (got ${it.version})") + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 358040f4e..2c20edba0 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -58,10 +58,11 @@ class DatabaseModule { @DbInternal migrations: Array, // @DbInternal dbTemplateStreamCallable: Callable, // openHelperFactory: DatabaseOpenHelperFactory, // + @DbInternal databaseName: String, // ): CryptomatorDatabase { LOG.i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) ThreadUtil.assumeNotMainThread() - return Room.databaseBuilder(context, CryptomatorDatabase::class.java, DATABASE_NAME) // + return Room.databaseBuilder(context, CryptomatorDatabase::class.java, databaseName) // .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // @@ -95,6 +96,11 @@ class DatabaseModule { return templateFactory.create().templateFile() } + @Singleton + @Provides + @DbInternal + internal fun provideDatabaseName(): String = DATABASE_NAME + @Singleton @Provides fun provideCloudDao(database: Invalidatable): CloudDao { From d65e6742d6b1b5c0ef39e972c173ea63d8cfb8e1 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 30 May 2024 01:25:58 +0200 Subject: [PATCH 084/120] Performed housekeeping Added missing message in "CryptomatorAssert" Fixed imports and codestyle Added missing call to "disableWriteAheadLogging" Made instance of "AOP_SQLiteDatabase" in "MappingSupportSQLiteDatabase" static --- .../java/org/cryptomator/data/db/CreateDatabaseTest.kt | 4 +++- .../java/org/cryptomator/data/db/CryptomatorAssert.java | 2 +- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 9 ++++++--- .../data/db/sqlmapping/MappingSupportSQLiteDatabase.kt | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index 31babc836..b91d25626 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -10,7 +10,9 @@ import androidx.test.platform.app.InstrumentationRegistry import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Test import org.junit.runner.RunWith diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java index 1b3951a0f..3ace6d7dc 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java +++ b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java @@ -38,7 +38,7 @@ private static void failCursorNotEquals(String message, Cursor expected, Cursor "{1}\n" + // "---------- but was ----------\n" + // "{2}", // - message != null && !Strings.isEmptyOrWhitespace(message) ? message : "", // + message != null && !Strings.isEmptyOrWhitespace(message) ? message : "Cursors are not equal", // CryptomatorDatabaseKt.stringify(expected), // CryptomatorDatabaseKt.stringify(actual)); fail(failMessage); diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index a01382df8..a195cca0c 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -87,6 +87,7 @@ class UpgradeDatabaseTest { //This needs to stay in sync with changes to DatabaseOpenHelperFactory/PatchedCallback db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { override fun onConfigure(db: SupportSQLiteDatabase) { + db.disableWriteAheadLogging() db.setForeignKeyConstraintsEnabled(true) } @@ -784,8 +785,8 @@ class UpgradeDatabaseTest { private fun indexStatement(db: SupportSQLiteDatabase): String { return Sql.SqlQueryBuilder("sqlite_master") // .columns(listOf("sql")) // - .where("type", Sql.eq("index")) - .where("name", Sql.eq("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID")) + .where("type", Sql.eq("index")) // + .where("name", Sql.eq("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID")) // .where("tbl_name", Sql.eq("VAULT_ENTITY")) // .executeOn(db).use { assertEquals(it.count, 1) @@ -916,7 +917,9 @@ class UpgradeDatabaseTest { db.version = 1 db.close() helper.runMigrationsAndValidate( - TEST_DB, 13, true, + TEST_DB, + 13, + true, Upgrade1To2(), Upgrade2To3(context), Upgrade3To4(), diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index ad644d7ac..99d62f995 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -75,14 +75,12 @@ internal class MappingSupportSQLiteDatabase( } override fun compileStatement(sql: String): SupportSQLiteStatement { - if(!isOpen) { + if (!isOpen) { throw SQLiteException("Database already closed") } return MappingSupportSQLiteStatement(sql) } - private val helper = AOP_SQLiteDatabase() - private fun map(sql: String): String { return mappingFunction.map(sql) } @@ -111,7 +109,7 @@ internal class MappingSupportSQLiteDatabase( private val bindings = mutableListOf<(SupportSQLiteStatement) -> Unit>() override fun bindBlob(index: Int, value: ByteArray) { - bindings.add { statement -> statement.bindBlob(index, value) } + bindings.add { statement -> statement.bindBlob(index, value.copyOf()) } } override fun bindDouble(index: Int, value: Double) { @@ -184,6 +182,8 @@ internal class MappingSupportSQLiteDatabase( } } +private val helper = AOP_SQLiteDatabase() + private class MappingSupportSQLiteOpenHelper( private val delegate: SupportSQLiteOpenHelper, private val mappingFunction: SQLMappingFunction From 01a8bd69635140aa51f5411461b4170f5404771a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 30 May 2024 21:21:31 +0200 Subject: [PATCH 085/120] Centralized database configuration This creates a single source of truth for configuring the main database and any tests. --- .../org/cryptomator/data/db/CorruptedDatabaseTest.kt | 7 +++---- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 8 +++----- .../java/org/cryptomator/data/db/CryptomatorDatabase.kt | 9 +++++++++ .../org/cryptomator/data/db/DatabaseOpenHelperFactory.kt | 4 +++- .../cryptomator/data/db/templating/DbTemplateModule.kt | 8 ++++---- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 27edac899..b91663123 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -101,10 +101,9 @@ private fun createVersion0Database(context: Context, databaseName: String) { val config = SupportSQLiteOpenHelper.Configuration.builder(context) // .name(databaseName) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { - override fun onConfigure(db: SupportSQLiteDatabase) { - db.disableWriteAheadLogging() - db.setForeignKeyConstraintsEnabled(true) - } + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = false // + ) override fun onCreate(db: SupportSQLiteDatabase) = throw InterruptCreationException() override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = throw IllegalStateException() diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index a195cca0c..d10f60c28 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -84,12 +84,10 @@ class UpgradeDatabaseTest { templateDbFile.copyTo(dbFile) } - //This needs to stay in sync with changes to DatabaseOpenHelperFactory/PatchedCallback db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { - override fun onConfigure(db: SupportSQLiteDatabase) { - db.disableWriteAheadLogging() - db.setForeignKeyConstraintsEnabled(true) - } + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = false // + ) override fun onCreate(db: SupportSQLiteDatabase) { fail("Database should not be created, but copied from template") diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 1b0912c95..943de5268 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -168,4 +168,13 @@ fun Cursor?.stringify(): String { private fun Array.uniqueToSet(): Set = toSet().also { require(this.size == it.size) { "Array contained ${this.size - it.size} duplicate elements." } +} + +fun SupportSQLiteDatabase.applyDefaultConfiguration(writeAheadLoggingEnabled: Boolean?) { + when (writeAheadLoggingEnabled) { + true -> enableWriteAheadLogging() + false -> disableWriteAheadLogging() + null -> {} + } + setForeignKeyConstraintsEnabled(true) } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index e40271785..88e7406ec 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -46,7 +46,9 @@ private class PatchedCallback( override fun onConfigure(db: SupportSQLiteDatabase) { LOG.d("Called onConfigure for \"${db.path}\"@${db.version}") - db.setForeignKeyConstraintsEnabled(true) + db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = null //WAL is handled by Room + ) // delegateCallback.onConfigure(db) // diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt index 3086751aa..170e80444 100644 --- a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt @@ -6,6 +6,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import org.cryptomator.data.db.DATABASE_NAME +import org.cryptomator.data.db.applyDefaultConfiguration import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named @@ -49,10 +50,9 @@ class DbTemplateModule { return SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { - override fun onConfigure(db: SupportSQLiteDatabase) { - db.disableWriteAheadLogging() - db.setForeignKeyConstraintsEnabled(true) - } + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = false // + ) override fun onCreate(db: SupportSQLiteDatabase) { Upgrade0To1().migrate(db) From 7f91090dcd692ec5bd2837b9a080d0edd81da7e8 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:30:53 +0200 Subject: [PATCH 086/120] Added logging for database corruption --- .../data/db/DatabaseOpenHelperFactory.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index 88e7406ec..a370d236c 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -78,13 +78,33 @@ private class PatchedCallback( } override fun onCorruption(db: SupportSQLiteDatabase) = useFinally({ + LOG.e(Exception(), "Called onCorruption for \"${db.path}\"") // delegateCallback.onCorruption(db) // }, finallyBlock = { invalidationCallback.invoke() + + logCorruptedDbState(db) }) + private fun logCorruptedDbState(db: SupportSQLiteDatabase) { + val state = db.path?.let { path -> + if (path == ":memory:") null else path + }.runCatching { + this?.let { path -> (File(path).exists()) } + }.map { + when (it) { + null -> "In memory" + true -> "Exists" + false -> "Deleted" + } + }.onFailure { verificationFailure -> + LOG.e(verificationFailure, "Couldn't verify state of database \"${db.path}\"") + }.getOrDefault("Unknown (see above)") + LOG.e(Exception(), "State of \"${db.path}\": $state") + } + override fun onOpen(db: SupportSQLiteDatabase) { // delegateCallback.onOpen(db) From 0937ba35e484cc0ace95ef73fa2993aeb836632e Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 13 May 2024 14:14:22 +0200 Subject: [PATCH 087/120] Added tests for verifying BOMs "PatchedCallback" and "DatabaseOpenHelperFactoryKt.patchConfiguration" interact with the inner classes of "SupportSQLiteOpenHelper" in a way that is prone to introducing bugs due to unnoticed changes to those classes. The tests introduced by this commit ensure that changes to the bills of materials for those classes are noticed and handled in the future. --- .../data/db/DatabaseOpenHelperFactoryTest.kt | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt new file mode 100644 index 000000000..84b6a8583 --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt @@ -0,0 +1,86 @@ +package org.cryptomator.data.db + +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.reflect.Field +import java.lang.reflect.Method + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DatabaseOpenHelperFactoryTest { + + @Test //For org.cryptomator.data.db.PatchedCallback + fun verifySupportSQLiteOpenHelperCallback() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.version: int", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onConfigure(androidx.sqlite.db.SupportSQLiteDatabase): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onCorruption(androidx.sqlite.db.SupportSQLiteDatabase): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onCreate(androidx.sqlite.db.SupportSQLiteDatabase): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onDowngrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onOpen(androidx.sqlite.db.SupportSQLiteDatabase): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int): void", + // + "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.Companion: androidx.sqlite.db.SupportSQLiteOpenHelper#Callback#Companion" + ) + assertEquals(bom, SupportSQLiteOpenHelper.Callback::class.java.getFieldAndMethodNames()) + } + + @Test //For org.cryptomator.data.db.DatabaseOpenHelperFactoryKt.patchConfiguration + fun verifySupportSQLiteOpenHelperConfiguration() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.allowDataLossOnRecovery: boolean", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.callback: androidx.sqlite.db.SupportSQLiteOpenHelper#Callback", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.context: android.content.Context", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.name: java.lang.String", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.useNoBackupDirectory: boolean", + // + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.Companion: androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Companion", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.builder(android.content.Context): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder" + ) + assertEquals(bom, SupportSQLiteOpenHelper.Configuration::class.java.getFieldAndMethodNames()) + } + + @Test //For org.cryptomator.data.db.DatabaseOpenHelperFactoryKt.patchConfiguration + fun verifySupportSQLiteOpenHelperConfigurationBuilder() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.allowDataLossOnRecovery(boolean): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.callback(androidx.sqlite.db.SupportSQLiteOpenHelper#Callback): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.name(java.lang.String): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.noBackupDirectory(boolean): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", + // + "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.build(): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration" + ) + assertEquals(bom, SupportSQLiteOpenHelper.Configuration.Builder::class.java.getFieldAndMethodNames()) + } +} + +private val DEFAULT_MEMBERS = setOf( + "java.lang.Object.equals(java.lang.Object): boolean", + "java.lang.Object.getClass(): java.lang.Class", + "java.lang.Object.hashCode(): int", + "java.lang.Object.notify(): void", + "java.lang.Object.notifyAll(): void", + "java.lang.Object.toString(): java.lang.String", + "java.lang.Object.wait(): void", + "java.lang.Object.wait(long): void", + "java.lang.Object.wait(long, int): void" +) + +private fun Class<*>.getFieldAndMethodNames(): Set { + val fieldsSequence = fields.asSequence().map { it.signature } + val methodsSequence = methods.asSequence().map { it.signature } + return (fieldsSequence + methodsSequence - DEFAULT_MEMBERS).toSet() +} + +private val Field.signature: String + get() = "${declaringClass.name}.$name: ${type.name}".replace('$', '#') + +private val Method.signature: String + get() { + val parameters = parameterTypes.asSequence().map { it.name }.joinToString(", ") + return "${declaringClass.name}.$name($parameters): ${returnType.name}".replace('$', '#') + } \ No newline at end of file From 93417c3f17424d2a33f41cb1248d0332756c77f8 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:09:16 +0200 Subject: [PATCH 088/120] Added tests for verifying BOMs for dependencies of "MappingSupportSQLiteDatabase" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/DatabaseOpenHelperFactoryTest.kt | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt index 84b6a8583..87b12c4b6 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt @@ -1,6 +1,9 @@ package org.cryptomator.data.db +import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.SupportSQLiteQuery +import androidx.sqlite.db.SupportSQLiteStatement import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import org.junit.Assert.assertEquals @@ -13,6 +16,95 @@ import java.lang.reflect.Method @SmallTest class DatabaseOpenHelperFactoryTest { + @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase + fun verifySupportSQLiteDatabase() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteDatabase.beginTransaction(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionNonExclusive(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener): void", + "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener): void", + "java.lang.AutoCloseable.close(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.compileStatement(java.lang.String): androidx.sqlite.db.SupportSQLiteStatement", + "androidx.sqlite.db.SupportSQLiteDatabase.delete(java.lang.String, java.lang.String, [Ljava.lang.Object;): int", + "androidx.sqlite.db.SupportSQLiteDatabase.disableWriteAheadLogging(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.enableWriteAheadLogging(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.endTransaction(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.execPerConnectionSQL(java.lang.String, [Ljava.lang.Object;): void", + "androidx.sqlite.db.SupportSQLiteDatabase.execSQL(java.lang.String): void", + "androidx.sqlite.db.SupportSQLiteDatabase.execSQL(java.lang.String, [Ljava.lang.Object;): void", + "androidx.sqlite.db.SupportSQLiteDatabase.getAttachedDbs(): java.util.List", + "androidx.sqlite.db.SupportSQLiteDatabase.getMaximumSize(): long", + "androidx.sqlite.db.SupportSQLiteDatabase.getPageSize(): long", + "androidx.sqlite.db.SupportSQLiteDatabase.getPath(): java.lang.String", + "androidx.sqlite.db.SupportSQLiteDatabase.getVersion(): int", + "androidx.sqlite.db.SupportSQLiteDatabase.inTransaction(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.insert(java.lang.String, int, android.content.ContentValues): long", + "androidx.sqlite.db.SupportSQLiteDatabase.isDatabaseIntegrityOk(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.isDbLockedByCurrentThread(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.isExecPerConnectionSQLSupported(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.isOpen(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.isReadOnly(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.isWriteAheadLoggingEnabled(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.needUpgrade(int): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.query(androidx.sqlite.db.SupportSQLiteQuery): android.database.Cursor", + "androidx.sqlite.db.SupportSQLiteDatabase.query(java.lang.String): android.database.Cursor", + "androidx.sqlite.db.SupportSQLiteDatabase.query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal): android.database.Cursor", + "androidx.sqlite.db.SupportSQLiteDatabase.query(java.lang.String, [Ljava.lang.Object;): android.database.Cursor", + "androidx.sqlite.db.SupportSQLiteDatabase.setForeignKeyConstraintsEnabled(boolean): void", + "androidx.sqlite.db.SupportSQLiteDatabase.setLocale(java.util.Locale): void", + "androidx.sqlite.db.SupportSQLiteDatabase.setMaxSqlCacheSize(int): void", + "androidx.sqlite.db.SupportSQLiteDatabase.setMaximumSize(long): long", + "androidx.sqlite.db.SupportSQLiteDatabase.setPageSize(long): void", + "androidx.sqlite.db.SupportSQLiteDatabase.setTransactionSuccessful(): void", + "androidx.sqlite.db.SupportSQLiteDatabase.setVersion(int): void", + "androidx.sqlite.db.SupportSQLiteDatabase.update(java.lang.String, int, android.content.ContentValues, java.lang.String, [Ljava.lang.Object;): int", + "androidx.sqlite.db.SupportSQLiteDatabase.yieldIfContendedSafely(): boolean", + "androidx.sqlite.db.SupportSQLiteDatabase.yieldIfContendedSafely(long): boolean" + ) + assertEquals(bom, SupportSQLiteDatabase::class.java.getFieldAndMethodNames()) + } + + @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement + fun verifySupportSQLiteStatement() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteProgram.bindBlob(int, [B): void", + "androidx.sqlite.db.SupportSQLiteProgram.bindDouble(int, double): void", + "androidx.sqlite.db.SupportSQLiteProgram.bindLong(int, long): void", + "androidx.sqlite.db.SupportSQLiteProgram.bindNull(int): void", + "androidx.sqlite.db.SupportSQLiteProgram.bindString(int, java.lang.String): void", + "androidx.sqlite.db.SupportSQLiteProgram.clearBindings(): void", + "java.lang.AutoCloseable.close(): void", + "androidx.sqlite.db.SupportSQLiteStatement.execute(): void", + "androidx.sqlite.db.SupportSQLiteStatement.executeInsert(): long", + "androidx.sqlite.db.SupportSQLiteStatement.executeUpdateDelete(): int", + "androidx.sqlite.db.SupportSQLiteStatement.simpleQueryForLong(): long", + "androidx.sqlite.db.SupportSQLiteStatement.simpleQueryForString(): java.lang.String" + ) + assertEquals(bom, SupportSQLiteStatement::class.java.getFieldAndMethodNames()) + } + + @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteQuery + fun verifySupportSQLiteQuery() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteQuery.bindTo(androidx.sqlite.db.SupportSQLiteProgram): void", + "androidx.sqlite.db.SupportSQLiteQuery.getArgCount(): int", + "androidx.sqlite.db.SupportSQLiteQuery.getSql(): java.lang.String" + ) + assertEquals(bom, SupportSQLiteQuery::class.java.getFieldAndMethodNames()) + } + + @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteOpenHelper + fun verifySupportSQLiteOpenHelper() { + val bom = setOf( + "androidx.sqlite.db.SupportSQLiteOpenHelper.close(): void", + "androidx.sqlite.db.SupportSQLiteOpenHelper.getDatabaseName(): java.lang.String", + "androidx.sqlite.db.SupportSQLiteOpenHelper.getReadableDatabase(): androidx.sqlite.db.SupportSQLiteDatabase", + "androidx.sqlite.db.SupportSQLiteOpenHelper.getWritableDatabase(): androidx.sqlite.db.SupportSQLiteDatabase", + "androidx.sqlite.db.SupportSQLiteOpenHelper.setWriteAheadLoggingEnabled(boolean): void" + ) + assertEquals(bom, SupportSQLiteOpenHelper::class.java.getFieldAndMethodNames()) + } + @Test //For org.cryptomator.data.db.PatchedCallback fun verifySupportSQLiteOpenHelperCallback() { val bom = setOf( From 01eab759fd1864e98daae1cd82149b71d2868833 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:10:33 +0200 Subject: [PATCH 089/120] Refactored tests for verifying BOMs Moved tests from android tests to unit tests Renamed containing class from "DatabaseOpenHelperFactoryTest" to "BomVerificationTest" Fixed failing tests due to differences in JDK distros *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../org/cryptomator/data/db/BomVerificationTest.kt} | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) rename data/src/{androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt => test/java/org/cryptomator/data/db/BomVerificationTest.kt} (97%) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt b/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt similarity index 97% rename from data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt rename to data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt index 87b12c4b6..ad7470d81 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseOpenHelperFactoryTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt @@ -4,17 +4,12 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.runner.RunWith import java.lang.reflect.Field import java.lang.reflect.Method -@RunWith(AndroidJUnit4::class) -@SmallTest -class DatabaseOpenHelperFactoryTest { +class BomVerificationTest { @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase fun verifySupportSQLiteDatabase() { @@ -23,7 +18,7 @@ class DatabaseOpenHelperFactoryTest { "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionNonExclusive(): void", "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener): void", "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener): void", - "java.lang.AutoCloseable.close(): void", + "java.io.Closeable.close(): void", "androidx.sqlite.db.SupportSQLiteDatabase.compileStatement(java.lang.String): androidx.sqlite.db.SupportSQLiteStatement", "androidx.sqlite.db.SupportSQLiteDatabase.delete(java.lang.String, java.lang.String, [Ljava.lang.Object;): int", "androidx.sqlite.db.SupportSQLiteDatabase.disableWriteAheadLogging(): void", @@ -73,7 +68,7 @@ class DatabaseOpenHelperFactoryTest { "androidx.sqlite.db.SupportSQLiteProgram.bindNull(int): void", "androidx.sqlite.db.SupportSQLiteProgram.bindString(int, java.lang.String): void", "androidx.sqlite.db.SupportSQLiteProgram.clearBindings(): void", - "java.lang.AutoCloseable.close(): void", + "java.io.Closeable.close(): void", "androidx.sqlite.db.SupportSQLiteStatement.execute(): void", "androidx.sqlite.db.SupportSQLiteStatement.executeInsert(): long", "androidx.sqlite.db.SupportSQLiteStatement.executeUpdateDelete(): int", From a198130d49a7a8e7cd7c151de0c58280b8a771b0 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:16:49 +0200 Subject: [PATCH 090/120] Improved tests for verifying BOMs Added sorting of BOMs to ease debugging Added check for classes that can't be verified by a unit test *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/BomVerificationTest.kt | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt b/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt index ad7470d81..b0762a3d2 100644 --- a/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/BomVerificationTest.kt @@ -8,12 +8,13 @@ import org.junit.Assert.assertEquals import org.junit.Test import java.lang.reflect.Field import java.lang.reflect.Method +import java.util.SortedSet class BomVerificationTest { @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase fun verifySupportSQLiteDatabase() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteDatabase.beginTransaction(): void", "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionNonExclusive(): void", "androidx.sqlite.db.SupportSQLiteDatabase.beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener): void", @@ -61,7 +62,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement fun verifySupportSQLiteStatement() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteProgram.bindBlob(int, [B): void", "androidx.sqlite.db.SupportSQLiteProgram.bindDouble(int, double): void", "androidx.sqlite.db.SupportSQLiteProgram.bindLong(int, long): void", @@ -80,7 +81,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteQuery fun verifySupportSQLiteQuery() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteQuery.bindTo(androidx.sqlite.db.SupportSQLiteProgram): void", "androidx.sqlite.db.SupportSQLiteQuery.getArgCount(): int", "androidx.sqlite.db.SupportSQLiteQuery.getSql(): java.lang.String" @@ -90,7 +91,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteOpenHelper fun verifySupportSQLiteOpenHelper() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteOpenHelper.close(): void", "androidx.sqlite.db.SupportSQLiteOpenHelper.getDatabaseName(): java.lang.String", "androidx.sqlite.db.SupportSQLiteOpenHelper.getReadableDatabase(): androidx.sqlite.db.SupportSQLiteDatabase", @@ -102,7 +103,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.PatchedCallback fun verifySupportSQLiteOpenHelperCallback() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.version: int", "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onConfigure(androidx.sqlite.db.SupportSQLiteDatabase): void", "androidx.sqlite.db.SupportSQLiteOpenHelper#Callback.onCorruption(androidx.sqlite.db.SupportSQLiteDatabase): void", @@ -118,7 +119,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.DatabaseOpenHelperFactoryKt.patchConfiguration fun verifySupportSQLiteOpenHelperConfiguration() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.allowDataLossOnRecovery: boolean", "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.callback: androidx.sqlite.db.SupportSQLiteOpenHelper#Callback", "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration.context: android.content.Context", @@ -133,7 +134,7 @@ class BomVerificationTest { @Test //For org.cryptomator.data.db.DatabaseOpenHelperFactoryKt.patchConfiguration fun verifySupportSQLiteOpenHelperConfigurationBuilder() { - val bom = setOf( + val bom = sortedSetOf( "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.allowDataLossOnRecovery(boolean): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.callback(androidx.sqlite.db.SupportSQLiteOpenHelper#Callback): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", "androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder.name(java.lang.String): androidx.sqlite.db.SupportSQLiteOpenHelper#Configuration#Builder", @@ -145,7 +146,7 @@ class BomVerificationTest { } } -private val DEFAULT_MEMBERS = setOf( +private val DEFAULT_MEMBERS = sortedSetOf( "java.lang.Object.equals(java.lang.Object): boolean", "java.lang.Object.getClass(): java.lang.Class", "java.lang.Object.hashCode(): int", @@ -157,10 +158,22 @@ private val DEFAULT_MEMBERS = setOf( "java.lang.Object.wait(long, int): void" ) -private fun Class<*>.getFieldAndMethodNames(): Set { +private val FORBIDDEN_CLASS_PREFIXES = setOf( + "android.", // + "java.", // + "javax.", // +) + +private fun Class<*>.getFieldAndMethodNames(): SortedSet { + for (prefix in FORBIDDEN_CLASS_PREFIXES) { + require(!name.startsWith(prefix)) { + "Class \"$name\" starts with invalid prefix \"$prefix\":\n" + + "Class is probably shipped with android and should not be validated in a non-android unit test." + } + } val fieldsSequence = fields.asSequence().map { it.signature } val methodsSequence = methods.asSequence().map { it.signature } - return (fieldsSequence + methodsSequence - DEFAULT_MEMBERS).toSet() + return (fieldsSequence + methodsSequence - DEFAULT_MEMBERS).toSortedSet() } private val Field.signature: String From 64ae9bd2230601bba0968a3cee52519bc3bae1d1 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:22:34 +0200 Subject: [PATCH 091/120] Added tests for "MappingSupportSQLiteStatement.newBoundStatement" Added inner class "MappingSupportSQLiteStatementTest" to "MappingSupportSQLiteDatabaseTest" and included an additional counter-based mapping Added test "testNewBoundStatementSingle": Run "newBoundStatement" three times on the same "MappingSupportSQLiteStatement" (with the same unmapped sql strings and the same backing mapping) Added test "testNewBoundStatementMultiple": Run "newBoundStatement" on three different instances of "MappingSupportSQLiteStatement" (with the same/different unmapped sql strings and per-statement backing mappings) Added matching data sets for tests Added a number of utility methods Refactored "cartesianProduct" methods Refactored extension methods for "ContentValues" defined in "MappingSupportSQLiteDatabaseTest.kt" and mirrored them for "Iterable?" Changed visibility of "MappingSupportSQLiteStatement.newBoundStatement" to "internal" and annotated it with "@VisibleForTesting" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabase.kt | 3 +- .../MappingSupportSQLiteDatabaseTest.kt | 434 +++++++++++++++++- 2 files changed, 423 insertions(+), 14 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 99d62f995..38f99603f 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -156,7 +156,8 @@ internal class MappingSupportSQLiteDatabase( return newBoundStatement().use { it.simpleQueryForString() } } - private fun newBoundStatement(): SupportSQLiteStatement { + @VisibleForTesting + internal fun newBoundStatement(): SupportSQLiteStatement { return delegate.compileStatement(map(sql)).also { statement -> for (binding: (SupportSQLiteStatement) -> Unit in bindings) { binding(statement) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 5b0d2ada9..5ec321843 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -16,11 +16,16 @@ import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteProgram import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteStatement +import org.cryptomator.data.db.sqlmapping.Mapping.COMMENT +import org.cryptomator.data.db.sqlmapping.Mapping.COUNTER +import org.cryptomator.data.db.sqlmapping.Mapping.IDENTITY import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNotSame +import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -37,11 +42,15 @@ import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.internal.verification.VerificationModeFactory.times import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.KInOrder import org.mockito.kotlin.anyArray import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder import org.mockito.kotlin.isNull +import org.mockito.stubbing.Answer import org.mockito.stubbing.OngoingStubbing +import java.util.LinkedList +import java.util.concurrent.atomic.AtomicInteger import java.util.stream.Stream import kotlin.streams.asStream @@ -355,8 +364,133 @@ class MappingSupportSQLiteDatabaseTest { assertNotSame(idStatement1, idStatement2) assertNotSame(commentStatement1, commentStatement2) } + + @Nested + inner class MappingSupportSQLiteStatementTest { + + private lateinit var counter: AtomicInteger + private lateinit var counterMapping: MappingSupportSQLiteDatabase + + @BeforeEach + fun beforeEachInner() { //Don't shadow "beforeEach" of outer class + counter = AtomicInteger(0) + counterMapping = MappingSupportSQLiteDatabase(delegateMock, object : SQLMappingFunction { + override fun map(sql: String): String = "$sql -- ${counter.getAndIncrement()}!" + override fun mapWhereClause(whereClause: String?): String = map(whereClause ?: "1 = 1") + override fun mapCursor(cursor: Cursor): Cursor = cursor + }) + } + + private fun resolveMapping(mapping: Mapping) = when (mapping) { + IDENTITY -> identityMapping + COMMENT -> commentMapping + COUNTER -> counterMapping + } + + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestNewBoundStatementSingle") + fun testNewBoundStatementSingle(statementData: Triple>, values: List?) { + val (mapping: MappingSupportSQLiteDatabase, call: String, expected: List) = statementData.resolve(::resolveMapping) + val expectedSize = expected.size + require(expectedSize >= 1) + + val mappingStatement = mapping.createAndBindStatement(call, values) + testConsecutiveNewBoundStatements(List(expectedSize) { statementData }, List(expectedSize) { values }) { _: Mapping, _: String, _: List? -> mappingStatement } + } + + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestNewBoundStatementMultiple") + fun testNewBoundStatementMultiple(statementData: List>>, values: List?>) { + testConsecutiveNewBoundStatements(statementData, values) + } + + private fun testConsecutiveNewBoundStatements(statementData: List>>, values: List?>) { + val mappingStatementSupplier = { mapping: Mapping, callSql: String, boundValues: List? -> + resolveMapping(mapping).createAndBindStatement(callSql, boundValues) + } + testConsecutiveNewBoundStatements(statementData, values, mappingStatementSupplier) + } + + private fun testConsecutiveNewBoundStatements( // + statementData: List>>, // + values: List?>, // + mappingStatementSupplier: (Mapping, String, List?) -> MappingSupportSQLiteStatement // + ) { + val statementCount = statementData.size + require(statementCount > 0) + require(values.size == statementCount) + + val expected = statementData.fold(emptyList() to 0) { acc, current -> + val nextExpectedValue = current.third.let { it.getOrNull(acc.second) ?: it.first() } + require(nextExpectedValue != SENTINEL) { + "Invalid test data; received sentinel to be added to ${acc.first} from $current @ ${acc.second}" + } + val nextExpectedAcc = acc.first + nextExpectedValue + val nextIndex = if (current.first == COUNTER) acc.second + 1 else acc.second + nextExpectedAcc to nextIndex + }.first + require(expected.size == statementCount) + + val compiledStatements = mutableListOf() + val newBoundStatementData = mutableListOf() + for ((index, entry) in statementData.withIndex()) { + val (compiledStatement: SupportSQLiteStatement, binding: Map) = mockSupportSQLiteStatement() + val mappingStatement = mappingStatementSupplier(entry.first, entry.second, values[index]) + compiledStatements.add(compiledStatement) + newBoundStatementData.add(NewBoundStatementData(mappingStatement, compiledStatement, expected[index], values[index], binding)) + } + + val order = inOrder(delegateMock, *compiledStatements.toTypedArray()) + order.verifyNoMoreInteractions() + verifyNoMoreInteractions(delegateMock) + + val results = expected.asSequence().zip(compiledStatements.asSequence()).groupBy({ it.first }) { it.second } + whenCalled(delegateMock.compileStatement(reifiedAnyOrNull())).then(throwingInvocationHandler(false, results)) + + repeat(statementCount) { index -> + val data = newBoundStatementData[index] + testSingleNewBoundStatement(order, data.mappingStatement, data.compiledStatement, data.expected, data.values, data.bindings, index == statementCount - 1) + } + + order.verifyNoMoreInteractions() + } + } + + private fun testSingleNewBoundStatement( + order: KInOrder, + mappingStatement: MappingSupportSQLiteStatement, + compiledStatement: SupportSQLiteStatement, + expected: String, + values: List?, + bindings: Map, + lastTest: Boolean + ) { + order.verifyNoMoreInteractions() + assertSame(compiledStatement, mappingStatement.newBoundStatement()) + + order.verify(delegateMock).compileStatement(expected) + if (lastTest) verifyNoMoreInteractions(delegateMock) + order.verify(compiledStatement, times(values.argCount())).bindString(anyInt(), anyString()) + order.verify(compiledStatement, times(values.nullCount())).bindNull(anyInt()) + order.verify(compiledStatement, times(values.argCount())).bindLong(anyInt(), anyLong()) + verifyNoMoreInteractions(compiledStatement) + + assertEquals(values.toBindingsMap(), bindings) + } } +private data class NewBoundStatementData( + val mappingStatement: MappingSupportSQLiteStatement, // + val compiledStatement: SupportSQLiteStatement, // + val expected: String, // + val values: List?, // + val bindings: Map, // +) + +enum class Mapping { IDENTITY, COMMENT, COUNTER } + +private const val SENTINEL = "::SENTINEL::" + private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set>): T? { return if (other != null) defaultArgThat(NullHandlingMatcher(pseudoEquals(other, valueExtractors), false)) else isNull() } @@ -484,6 +618,20 @@ private val contentValuesProperties: Set> private val DUMMY_CURSOR: Cursor get() = MatrixCursor(arrayOf()) +private fun throwingInvocationHandler(retainLast: Boolean, handledResults: Map>): Answer = object : Answer { + val values: Map> = handledResults.asSequence().map { entry -> + require(entry.value.isNotEmpty()) + entry.key to LinkedList(entry.value) + }.toMap() + + override fun answer(invocation: InvocationOnMock): R { + val argument = invocation.getArgument(0) + val resultsForArg = requireNotNull(values[argument]) { "Undefined invocation $invocation" } + require(resultsForArg.isNotEmpty()) { "No results for invocation $invocation" } + return if (resultsForArg.size == 1 && retainLast) resultsForArg.first() else resultsForArg.removeFirst() + } +} + private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { val mock = mock(CancellationSignal::class.java) whenCalled(mock.isCanceled).thenReturn(isCanceled) @@ -523,6 +671,12 @@ private fun mockContentValues(entries: Map): ContentValues { return mock } +private fun MappingSupportSQLiteDatabase.createAndBindStatement(sql: String, values: List?): MappingSupportSQLiteStatement { + val mappingStatement = this.MappingSupportSQLiteStatement(sql) + SimpleSQLiteQuery.bind(mappingStatement, values?.toTypedArray()) + return mappingStatement +} + data class CallData( val idCall: T, val commentCall: T, @@ -537,6 +691,8 @@ data class CallDataTwo( val commentExpected: E ) +private fun Triple>.resolve(resolver: (Mapping) -> MappingSupportSQLiteDatabase) = Triple(resolver(first), second, third) + fun sourceForTestQueryCancelable(): Stream { val queries = sequenceOf( CallData( @@ -567,7 +723,7 @@ fun sourceForTestQueryCancelable(): Stream { ) ) - return queries.cartesianProduct(signals).map { it.toList() }.toArgumentsStream() + return queries.cartesianProductTwo(signals).map { it.toList() }.toArgumentsStream() } fun sourceForTestInsert(): Stream> = sequenceOf( @@ -695,16 +851,262 @@ fun sourceForTestUpdate(): Stream { ) ) - return contentValues.cartesianProduct(whereClauses).cartesianProduct(whereArgs).map { it.toList() }.toArgumentsStream() + return contentValues.cartesianProductTwo(whereClauses).cartesianProductThree(whereArgs).map { it.toList() }.toArgumentsStream() } -@JvmName("cartesianProductTwo") -fun Sequence.cartesianProduct(other: Iterable): Sequence> = flatMap { a -> +private val newBoundStatementValues = listOf?>( // + //The ContentValues in this dataset always have the following order and counts: + //String [0,2], null[0,1], Int[0,1] + //This makes the ordered verification a lot easier + null, // + listOf(), // + listOf(null), // + listOf("value"), // + listOf("value1", "value2"), // + listOf("value", 10101), // + listOf("value", null), // + listOf("value", null, 10101) // +) + +fun sourceForTestNewBoundStatementSingle(): Stream { + val statementData = sequenceOf( // + Triple( // + IDENTITY, "INSERT INTO `id_test` (`id_col`) VALUES (?)", listOf( // + "INSERT INTO `id_test` (`id_col`) VALUES (?)" // + ) + ), Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 1!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 2!" // + ) + ), Triple( // + IDENTITY, "SELECT count(*) FROM `id_test`", listOf( // + "SELECT count(*) FROM `id_test`" // + ) + ), Triple( // + COMMENT, "SELECT count(*) FROM `comment_test`", listOf( // + "SELECT count(*) FROM `comment_test` -- Comment!" // + ) + ), Triple( // + COUNTER, "SELECT count(*) FROM `counter_test`", listOf( // + "SELECT count(*) FROM `counter_test` -- 0!", // + "SELECT count(*) FROM `counter_test` -- 1!", // + "SELECT count(*) FROM `counter_test` -- 2!" // + ) + ), Triple( // + IDENTITY, "DELETE FROM `id_test` WHERE `id_col1` = 'id_value' AND `id_col2` = ?", listOf( // + "DELETE FROM `id_test` WHERE `id_col1` = 'id_value' AND `id_col2` = ?" // + ) + ), Triple( // + COMMENT, "DELETE FROM `comment_test` WHERE `comment_col1` = 'comment_value' AND `comment_col2` = ?", listOf( // + "DELETE FROM `comment_test` WHERE `comment_col1` = 'comment_value' AND `comment_col2` = ? -- Comment!" // + ) + ), Triple( // + COUNTER, "DELETE FROM `counter_test` WHERE `counter_col1` = 'counter_value' AND `counter_col2` = ?", listOf( // + "DELETE FROM `counter_test` WHERE `counter_col1` = 'counter_value' AND `counter_col2` = ? -- 0!", // + "DELETE FROM `counter_test` WHERE `counter_col1` = 'counter_value' AND `counter_col2` = ? -- 1!", // + "DELETE FROM `counter_test` WHERE `counter_col1` = 'counter_value' AND `counter_col2` = ? -- 2!" // + ) + ) + ) + return statementData // + .cartesianProductTwo(newBoundStatementValues) // + .map { it.toList() } // + .toArgumentsStream() +} + +fun sourceForTestNewBoundStatementMultiple(): Stream { + //result.count() == 6 * 18 == 108 + val statementData = listOf( // + Triple( // + Triple( // + IDENTITY, "INSERT INTO `id_test` (`id_col`) VALUES (?)", listOf( // + "INSERT INTO `id_test` (`id_col`) VALUES (?)" // + ) + ), // + Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), // + Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + SENTINEL, // + SENTINEL // + ) + ) + ), // + Triple( // + Triple( // + COMMENT, "INSERT INTO `comment_test1` (`comment_col1`) VALUES (?)", listOf( // + "INSERT INTO `comment_test1` (`comment_col1`) VALUES (?) -- Comment!" // + ) + ), // + Triple( // + COMMENT, "INSERT INTO `comment_test2` (`comment_col2`) VALUES (?)", listOf( // + "INSERT INTO `comment_test2` (`comment_col2`) VALUES (?) -- Comment!" // + ) + ), // + Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + SENTINEL, // + SENTINEL // + ) + ) + ), // + Triple( // + Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + SENTINEL, // + SENTINEL // + ) + ), // + Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), // + Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + SENTINEL, // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 1!", // + SENTINEL // + ) + ) + ), // + Triple( // + Triple( // + COUNTER, "INSERT INTO `counter_test1` (`counter_col1`) VALUES (?)", listOf( // + "INSERT INTO `counter_test1` (`counter_col1`) VALUES (?) -- 0!", // + SENTINEL, // + SENTINEL // + ) + ), // + Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), // + Triple( // + COUNTER, "INSERT INTO `counter_test2` (`counter_col2`) VALUES (?)", listOf( // + SENTINEL, // + "INSERT INTO `counter_test2` (`counter_col2`) VALUES (?) -- 1!", // + SENTINEL // + ) + ) + ), // + Triple( // + Triple( // + COUNTER, "DELETE FROM `counter_test`", listOf( // + "DELETE FROM `counter_test` -- 0!", // + SENTINEL, // + SENTINEL // + ) + ), // + Triple( // + COUNTER, "DELETE FROM `counter_test`", listOf( // + SENTINEL, // + "DELETE FROM `counter_test` -- 1!", // + SENTINEL // + ) + ), // + Triple( // + COUNTER, "DELETE FROM `counter_test`", listOf( // + SENTINEL, // + SENTINEL, // + "DELETE FROM `counter_test` -- 2!" // + ) + ) + ), // + Triple( // + Triple( // + COUNTER, "INSERT INTO `counter_test1` (`counter_col1`) VALUES (?)", listOf( // + "INSERT INTO `counter_test1` (`counter_col1`) VALUES (?) -- 0!", // + SENTINEL, // + SENTINEL // + ) + ), // + Triple( // + COUNTER, "DELETE FROM `counter_test2`", listOf( // + SENTINEL, // + "DELETE FROM `counter_test2` -- 1!", // + SENTINEL // + ) + ), // + Triple( // + COUNTER, "SELECT count(*) FROM `counter_test3` WHERE `counter_col3` = ?", listOf( // + SENTINEL, // + SENTINEL, // + "SELECT count(*) FROM `counter_test3` WHERE `counter_col3` = ? -- 2!" // + ) + ) + ) + ) + val values = listOf( // + Triple( // + null, null, null // + ), Triple( // + listOf(), listOf(), listOf() // + ), Triple( // + listOf(null), listOf(null), listOf(null) // + ), Triple( // + listOf("value"), listOf("value"), listOf("value") // + ), Triple( // + listOf("value"), listOf(null), listOf("value") // + ), Triple( // + listOf("value1"), listOf("value2"), listOf("value3") // + ), Triple( // + listOf("value"), listOf(2_000_0), listOf() // + ), Triple( // + listOf("value"), listOf(2_000_0), listOf(null) // + ), Triple( // + listOf("value", "value"), listOf("value"), listOf() // + ), Triple( // + listOf("value1-1", "value1-2"), listOf("value2-1"), listOf() // + ), Triple( // + listOf("value1-1", 1_000_2), listOf(null), listOf() // + ), Triple( // + listOf("value", "value", "value"), listOf("value"), listOf() // + ), Triple( // + listOf("value", "value", "value"), listOf("value", "value", "value"), listOf("value", "value", "value") // + ), Triple( // + listOf("value1", "value2", "value3"), listOf("value1", "value2", "value3"), listOf("value1", "value2", "value3") // + ), Triple( // + listOf("value1", "value1", "value1"), listOf("value2", "value2", "value2"), listOf("value3", "value3", "value3") // + ), Triple( // + listOf("value1-1", "value1-2", "value1-3"), listOf("value2-1", "value2-2", "value2-3"), listOf("value3-1", "value3-2", "value3-3") // + ), Triple( // + listOf("value1-1", "value1-2", null), listOf("value2-1", null, null), listOf("value3-1", "value3-2", "value3-3") // + ), Triple( // + listOf("value1-1", null, 1_000_3), listOf("value2-1", 2_000_2, 2_000_3), listOf(null, null, 3_000_3) // + ) + ) + + val statementDataSets: Sequence>>> = statementData.asSequence() // + .map { it.toList() } + + val valueSets: List?>> = values.asSequence() // + .map { it.toList() } // + .toList() + val result: Sequence>>, List?>>> = statementDataSets.cartesianProductTwo(valueSets) + return result // + .map { it.toList() } // + .toArgumentsStream() +} + +fun Sequence.cartesianProductTwo(other: Iterable): Sequence> = flatMap { a -> other.asSequence().map { b -> a to b } } -@JvmName("cartesianProductThree") -fun Sequence>.cartesianProduct(other: Iterable): Sequence> = flatMap { abPair -> +fun Sequence>.cartesianProductThree(other: Iterable): Sequence> = flatMap { abPair -> other.asSequence().map { c -> Triple(abPair.first, abPair.second, c) } } @@ -713,13 +1115,19 @@ fun Sequence>.toArgumentsStream(): Stream = map { }.asStream() -private fun ContentValues.nullCount(): Int = valueSet().count { it.value == null } +private fun ContentValues.nullCount(): Int = valueSet().asSequence().map { it.value }.asIterable().nullCount() + +private inline fun ContentValues.argCount(): Int = valueSet().asSequence().map { it.value }.asIterable().argCount() + +private fun ContentValues.toBindingsMap(): Map = valueSet().asSequence().map { it.value }.asIterable().toBindingsMap() + +private fun Iterable?.nullCount(): Int = this?.count { it == null } ?: 0 -private inline fun ContentValues.argCount(): Int = valueSet().asSequence().map { it.value }.filterIsInstance().count() +private inline fun Iterable?.argCount(): Int = this?.asSequence()?.filterIsInstance()?.count() ?: 0 -private fun ContentValues.toBindingsMap(): Map { - return valueSet().map { it.value } // - .map { if (it is Int) it.toLong() else it } // Required because java.lang.Integer.valueOf(x) != java.lang.Long.valueOf(x) - .mapIndexed { index, value -> index + 1 to value } // - .toMap() +private fun Iterable?.toBindingsMap(): Map { + return this?.asSequence() // + ?.map { if (it is Int) it.toLong() else it } // Required because java.lang.Integer.valueOf(x) != java.lang.Long.valueOf(x) + ?.mapIndexed { index, value -> index + 1 to value } // + ?.toMap() ?: emptyMap() } \ No newline at end of file From e475391797d96cdb73deb554336e22f316c8e4dc Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:45:29 +0200 Subject: [PATCH 092/120] Added large tests for "MappingSupportSQLiteStatement.newBoundStatement" Also added matching data sets Added method "cartesianProductFour" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 5ec321843..6cf14e226 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -404,6 +405,20 @@ class MappingSupportSQLiteDatabaseTest { testConsecutiveNewBoundStatements(statementData, values) } + @EnabledIfEnvironmentVariable(named = "RUN_VERY_LARGE_TESTS", matches = "(?i)true|1|yes", disabledReason = "Very large tests are disabled") + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestNewBoundStatementNumerous1") + fun testNewBoundStatementNumerous1(statementData: List>>, values: List?>) { + testConsecutiveNewBoundStatements(statementData, values) + } + + @EnabledIfEnvironmentVariable(named = "RUN_VERY_LARGE_TESTS", matches = "(?i)true|1|yes", disabledReason = "Very large tests are disabled") + @ParameterizedTest + @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestNewBoundStatementNumerous2") + fun testNewBoundStatementNumerous2(statementData: List>>, values: List?>) { + testConsecutiveNewBoundStatements(statementData, values) + } + private fun testConsecutiveNewBoundStatements(statementData: List>>, values: List?>) { val mappingStatementSupplier = { mapping: Mapping, callSql: String, boundValues: List? -> resolveMapping(mapping).createAndBindStatement(callSql, boundValues) @@ -920,6 +935,16 @@ fun sourceForTestNewBoundStatementSingle(): Stream { .toArgumentsStream() } +private val newBoundStatementValuesSmall = listOf?>( // + //The ContentValues in this dataset always have the following order and counts: + //String [0,2], null[0,1], Int[0,1] + //This makes the ordered verification a lot easier + null, // + listOf(), // + listOf("value"), // + listOf("value", null, 10101) // +) + fun sourceForTestNewBoundStatementMultiple(): Stream { //result.count() == 6 * 18 == 108 val statementData = listOf( // @@ -1102,6 +1127,80 @@ fun sourceForTestNewBoundStatementMultiple(): Stream { .toArgumentsStream() } +fun sourceForTestNewBoundStatementNumerous1(): Stream { + //result.count() == 3 ^ 3 * 4 ^ 3 == 1,728 + val statementData = listOf( // + Triple( // + IDENTITY, "INSERT INTO `id_test` (`id_col`) VALUES (?)", listOf( // + "INSERT INTO `id_test` (`id_col`) VALUES (?)" // + ) + ), Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 1!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 2!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 3!" // + ) + ) + ) + + val statementDataSets: Sequence>>> = statementData.asSequence() // + .cartesianProductTwo(statementData) // + .cartesianProductThree(statementData) // + .map { it.toList() } + + val valueSets: List?>> = newBoundStatementValuesSmall.asSequence() // + .cartesianProductTwo(newBoundStatementValuesSmall) // + .cartesianProductThree(newBoundStatementValuesSmall) // + .map { it.toList() } // + .toList() + val result: Sequence>>, List?>>> = statementDataSets.cartesianProductTwo(valueSets) + return result // + .map { it.toList() } // + .toArgumentsStream() +} + +fun sourceForTestNewBoundStatementNumerous2(): Stream { + //result.count() == 3 ^ 4 * 8 ^ 4 == 331,776 + val statementData = listOf( // + Triple( // + IDENTITY, "INSERT INTO `id_test` (`id_col`) VALUES (?)", listOf( // + "INSERT INTO `id_test` (`id_col`) VALUES (?)" // + ) + ), Triple( // + COMMENT, "INSERT INTO `comment_test` (`comment_col`) VALUES (?)", listOf( // + "INSERT INTO `comment_test` (`comment_col`) VALUES (?) -- Comment!" // + ) + ), Triple( // + COUNTER, "INSERT INTO `counter_test` (`counter_col`) VALUES (?)", listOf( // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 0!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 1!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 2!", // + "INSERT INTO `counter_test` (`counter_col`) VALUES (?) -- 3!" // + ) + ) + ) + + val statementDataSets: Sequence>>> = statementData.asSequence() // + .cartesianProductTwo(statementData) // + .cartesianProductThree(statementData) // + .cartesianProductFour(statementData) + + val valueSets: List?>> = newBoundStatementValues.asSequence() // + .cartesianProductTwo(newBoundStatementValues) // + .cartesianProductThree(newBoundStatementValues) // + .cartesianProductFour(newBoundStatementValues) // + .toList() + val result: Sequence>>, List?>>> = statementDataSets.cartesianProductTwo(valueSets) + return result // + .map { it.toList() } // + .toArgumentsStream() +} + fun Sequence.cartesianProductTwo(other: Iterable): Sequence> = flatMap { a -> other.asSequence().map { b -> a to b } } @@ -1110,6 +1209,10 @@ fun Sequence>.cartesianProductThree(other: Iterable): Se other.asSequence().map { c -> Triple(abPair.first, abPair.second, c) } } +fun Sequence>.cartesianProductFour(other: Iterable): Sequence> = flatMap { triple -> + other.asSequence().map { otherElement -> listOf(triple.first, triple.second, triple.third, otherElement) } +} + fun Sequence>.toArgumentsStream(): Stream = map { Arguments { it.toTypedArray() } }.asStream() From 2897603055dde93fff77bbe0eaab762f3c85d8fa Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:46:29 +0200 Subject: [PATCH 093/120] Refactored "testInsert" and tests in "MappingSupportSQLiteStatementTest" Unified the implementation of "testInsert" with the implemention of all tests contained in "MappingSupportSQLiteStatementTest" by first augmenting and moving the implementation of "testSingleNewBoundStatement" to a new method "testSingleCompiledStatement" and then using that method in "testInsert": > Previous control flows: >> "testNewBoundStatementX" -> ("testConsecutiveNewBoundStatements" ->) "testConsecutiveNewBoundStatements" -> "testSingleNewBoundStatement" -> *Verifications* >> "testInsert" -> *Verifications* > New control flows: >> "testNewBoundStatementX" -> ("testConsecutiveNewBoundStatements" ->) "testConsecutiveNewBoundStatements" -> "testSingleNewBoundStatement" -> "testSingleCompiledStatement" -> *Verifications* >> "testInsert" -> "testSingleInsert" -> "testSingleCompiledStatement" -> *Verifications* Removed extensions methods for "ContentValues" defined in "MappingSupportSQLiteDatabaseTest.kt" and replaced usages with calls to corresponding extension methods for "Iterable?" defined in "MappingSupportSQLiteDatabaseTest.kt" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 6cf14e226..a670eb704 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -188,32 +188,28 @@ class MappingSupportSQLiteDatabaseTest { whenCalled(delegateMock.compileStatement(arguments.commentExpected)).thenReturn(commentCompiledStatement) val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) - identityMapping.insert("id_test", 1, arguments.idCall) - order.verify(delegateMock).compileStatement(arguments.idExpected) - order.verify(idCompiledStatement, times(arguments.idCall.argCount())).bindString(anyInt(), anyString()) - order.verify(idCompiledStatement, times(arguments.idCall.nullCount())).bindNull(anyInt()) - order.verify(idCompiledStatement, times(arguments.idCall.argCount())).bindLong(anyInt(), anyLong()) - order.verify(idCompiledStatement).executeInsert() - order.verify(idCompiledStatement).close() - verifyNoMoreInteractions(idCompiledStatement) - - order.verifyNoMoreInteractions() - commentMapping.insert("comment_test", 2, arguments.commentCall) - - order.verify(delegateMock).compileStatement(arguments.commentExpected) - /* */ verifyNoMoreInteractions(delegateMock) - order.verify(commentCompiledStatement, times(arguments.commentCall.argCount())).bindString(anyInt(), anyString()) - order.verify(commentCompiledStatement, times(arguments.commentCall.nullCount())).bindNull(anyInt()) - order.verify(commentCompiledStatement, times(arguments.commentCall.argCount())).bindLong(anyInt(), anyLong()) - order.verify(commentCompiledStatement).executeInsert() - order.verify(commentCompiledStatement).close() - verifyNoMoreInteractions(commentCompiledStatement) + testSingleInsert(order, { identityMapping.insert("id_test", 1, arguments.idCall) }, idCompiledStatement, arguments.idExpected, arguments.idCall, idBindings, false) + testSingleInsert(order, { commentMapping.insert("comment_test", 2, arguments.commentCall) }, commentCompiledStatement, arguments.commentExpected, arguments.commentCall, commentBindings, true) order.verifyNoMoreInteractions() + } - assertEquals(arguments.idCall.toBindingsMap(), idBindings) - assertEquals(arguments.commentCall.toBindingsMap(), commentBindings) + private fun testSingleInsert( // + order: KInOrder, // + toVerify: () -> Unit, // + compiledStatement: SupportSQLiteStatement, // + expected: String, // + values: ContentValues, // + bindings: Map, // + lastTest: Boolean // + ) { + val valueSet: Set?> = values.valueSet() + val alsoVerify = { + order.verify(compiledStatement).executeInsert() + order.verify(compiledStatement).close() + } + testSingleCompiledStatement(order, toVerify, compiledStatement, expected, valueSet.asSequence().requireNoNulls().map { it.value }.toList(), bindings, alsoVerify, lastTest) } @Test @@ -471,23 +467,37 @@ class MappingSupportSQLiteDatabaseTest { } } - private fun testSingleNewBoundStatement( - order: KInOrder, - mappingStatement: MappingSupportSQLiteStatement, - compiledStatement: SupportSQLiteStatement, - expected: String, - values: List?, - bindings: Map, - lastTest: Boolean + private fun testSingleNewBoundStatement( // + order: KInOrder, // + mappingStatement: MappingSupportSQLiteStatement, // + compiledStatement: SupportSQLiteStatement, // + expected: String, // + values: List?, // + bindings: Map, // + lastTest: Boolean // + ) = testSingleCompiledStatement( // + order, { assertSame(compiledStatement, mappingStatement.newBoundStatement()) }, compiledStatement, expected, values, bindings, { }, lastTest // + ) + + private fun testSingleCompiledStatement( // + order: KInOrder, // + toVerify: () -> Unit, // + compiledStatement: SupportSQLiteStatement, // + expected: String, // + values: List?, // + bindings: Map, // + alsoVerify: () -> Unit, // + lastTest: Boolean // ) { order.verifyNoMoreInteractions() - assertSame(compiledStatement, mappingStatement.newBoundStatement()) + toVerify() order.verify(delegateMock).compileStatement(expected) if (lastTest) verifyNoMoreInteractions(delegateMock) order.verify(compiledStatement, times(values.argCount())).bindString(anyInt(), anyString()) order.verify(compiledStatement, times(values.nullCount())).bindNull(anyInt()) order.verify(compiledStatement, times(values.argCount())).bindLong(anyInt(), anyLong()) + alsoVerify() verifyNoMoreInteractions(compiledStatement) assertEquals(values.toBindingsMap(), bindings) @@ -1217,13 +1227,6 @@ fun Sequence>.toArgumentsStream(): Stream = map { Arguments { it.toTypedArray() } }.asStream() - -private fun ContentValues.nullCount(): Int = valueSet().asSequence().map { it.value }.asIterable().nullCount() - -private inline fun ContentValues.argCount(): Int = valueSet().asSequence().map { it.value }.asIterable().argCount() - -private fun ContentValues.toBindingsMap(): Map = valueSet().asSequence().map { it.value }.asIterable().toBindingsMap() - private fun Iterable?.nullCount(): Int = this?.count { it == null } ?: 0 private inline fun Iterable?.argCount(): Int = this?.asSequence()?.filterIsInstance()?.count() ?: 0 From 93997c44d3e6113df9247ef2c242e99dd3670ed4 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:47:15 +0200 Subject: [PATCH 094/120] Refactored "testInsertConflictAlgorithms" to also use "testSingleInsert" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index a670eb704..8de5e1231 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -226,34 +226,23 @@ class MappingSupportSQLiteDatabaseTest { @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestInsertConflictAlgorithms") fun testInsertConflictAlgorithms(arguments: Triple) { val (conflictAlgorithm: Int, idStatement: String, commentStatement: String) = arguments + val idContentValues = mockContentValues("col1" to "val1") //Inlining this declaration causes problems for some reason + val commentContentValues = mockContentValues("col2" to "val2") - val idCompiledStatement = mock(SupportSQLiteStatement::class.java) - val commentCompiledStatement = mock(SupportSQLiteStatement::class.java) + val (idCompiledStatement: SupportSQLiteStatement, idBindings: Map) = mockSupportSQLiteStatement() + val (commentCompiledStatement: SupportSQLiteStatement, commentBindings: Map) = mockSupportSQLiteStatement() whenCalled(delegateMock.compileStatement(idStatement)).thenReturn(idCompiledStatement) whenCalled(delegateMock.compileStatement(commentStatement)).thenReturn(commentCompiledStatement) val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) - val idContentValues = mockContentValues("col1" to "val1") //Inlining this declaration causes problems for some reason - assertDoesNotThrow { identityMapping.insert("id_test", conflictAlgorithm, idContentValues) } - - order.verify(delegateMock).compileStatement(idStatement) - order.verify(idCompiledStatement).bindString(1, "val1") - order.verify(idCompiledStatement).executeInsert() - order.verify(idCompiledStatement).close() - verifyNoMoreInteractions(idCompiledStatement) - - order.verifyNoMoreInteractions() - val commentContentValues = mockContentValues("col2" to "val2") - assertDoesNotThrow { commentMapping.insert("comment_test", conflictAlgorithm, commentContentValues) } - - order.verify(delegateMock).compileStatement(commentStatement) - /* */ verifyNoMoreInteractions(delegateMock) - order.verify(commentCompiledStatement).bindString(1, "val2") - order.verify(commentCompiledStatement).executeInsert() - order.verify(commentCompiledStatement).close() - verifyNoMoreInteractions(commentCompiledStatement) + testSingleInsert( // + order, { assertDoesNotThrow { identityMapping.insert("id_test", conflictAlgorithm, idContentValues) } }, idCompiledStatement, idStatement, idContentValues, idBindings, false // + ) + testSingleInsert( // + order, { assertDoesNotThrow { commentMapping.insert("comment_test", conflictAlgorithm, commentContentValues) } }, commentCompiledStatement, commentStatement, commentContentValues, commentBindings, true // + ) order.verifyNoMoreInteractions() } From ec9dfffad79cafb8498f200d3238423efed28629 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:47:49 +0200 Subject: [PATCH 095/120] Reformatted "MappingSupportSQLiteDatabaseTest.kt" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 310 +++++++++--------- 1 file changed, 147 insertions(+), 163 deletions(-) diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 8de5e1231..79f60d41a 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -132,10 +132,10 @@ class MappingSupportSQLiteDatabaseTest { commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test`")) val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() - verify(delegateMock).query( + verify(delegateMock).query( // anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), supportSQLiteQueryProperties) ) - verify(delegateMock).query( + verify(delegateMock).query( // anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!"), supportSQLiteQueryProperties) ) verifyNoMoreInteractions(delegateMock) @@ -149,10 +149,10 @@ class MappingSupportSQLiteDatabaseTest { commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2"))) val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() - verify(delegateMock).query( + verify(delegateMock).query( // anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), supportSQLiteQueryProperties) ) - verify(delegateMock).query( + verify(delegateMock).query( // anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")), supportSQLiteQueryProperties) ) verifyNoMoreInteractions(delegateMock) @@ -167,12 +167,12 @@ class MappingSupportSQLiteDatabaseTest { commentMapping.query(queries.commentCall, signals.commentCall) val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() - verify(delegateMock).query( - anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), + verify(delegateMock).query( // + anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), // anyPseudoEqualsUnlessNull(signals.idExpected, setOf>(CancellationSignal::isCanceled)) ) - verify(delegateMock).query( - anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), + verify(delegateMock).query( // + anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), // anyPseudoEqualsUnlessNull(signals.commentExpected, setOf>(CancellationSignal::isCanceled)) ) verifyNoMoreInteractions(delegateMock) @@ -518,9 +518,9 @@ private fun pseudoEquals(other: T, valueExtractors: Set( - private val other: T, - private val valueExtractors: Set> +private class PseudoEqualsMatcher( // + private val other: T, // + private val valueExtractors: Set> // ) : ArgumentMatcher { override fun matches(argument: T): Boolean { @@ -573,9 +573,9 @@ private fun ValueExtractor.asCached(): ValueExtractor { private inline fun OngoingStubbing.thenDo(crossinline action: (invocation: InvocationOnMock) -> Unit): OngoingStubbing = thenAnswer { action(it) } -private class NullHandlingMatcher( - private val delegate: ArgumentMatcher, - private val matchNull: Boolean +private class NullHandlingMatcher( // + private val delegate: ArgumentMatcher, // + private val matchNull: Boolean // ) : ArgumentMatcher { override fun matches(argument: T?): Boolean { @@ -586,12 +586,12 @@ private class NullHandlingMatcher( } } -private fun newCachedSupportSQLiteQueryProperties(): Set> = setOf( - SupportSQLiteQuery::sql.asCached(), - SupportSQLiteQuery::argCount, - { query: SupportSQLiteQuery -> - CachingSupportSQLiteProgram().also { query.bindTo(it) }.bindings - }.asCached() +private fun newCachedSupportSQLiteQueryProperties(): Set> = setOf( // + SupportSQLiteQuery::sql.asCached(), // + SupportSQLiteQuery::argCount, // + { query: SupportSQLiteQuery -> // + CachingSupportSQLiteProgram().also { query.bindTo(it) }.bindings // + }.asCached() // ) private class CachingSupportSQLiteProgram : SupportSQLiteProgram { @@ -625,7 +625,7 @@ private class CachingSupportSQLiteProgram : SupportSQLiteProgram { } private val contentValuesProperties: Set> - get() = setOf( + get() = setOf( // ContentValues::valueSet ) @@ -691,177 +691,161 @@ private fun MappingSupportSQLiteDatabase.createAndBindStatement(sql: String, val return mappingStatement } -data class CallData( - val idCall: T, - val commentCall: T, - val idExpected: T, - val commentExpected: T +data class CallData( // + val idCall: T, // + val commentCall: T, // + val idExpected: T, // + val commentExpected: T // ) -data class CallDataTwo( - val idCall: C, - val commentCall: C, - val idExpected: E, - val commentExpected: E +data class CallDataTwo( // + val idCall: C, // + val commentCall: C, // + val idExpected: E, // + val commentExpected: E // ) private fun Triple>.resolve(resolver: (Mapping) -> MappingSupportSQLiteDatabase) = Triple(resolver(first), second, third) fun sourceForTestQueryCancelable(): Stream { - val queries = sequenceOf( - CallData( - SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), - SimpleSQLiteQuery("SELECT `col` FROM `comment_test`"), - SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), - SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!") - ), - CallData( - SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), - SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")), - SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), - SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) + val queries = sequenceOf( // + CallData( // + SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), // + SimpleSQLiteQuery("SELECT `col` FROM `comment_test`"), // + SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), // + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!") // + ), CallData( // + SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), // + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")), // + SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), // + SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")) // ) ) - val signals = listOf>( - CallData( - mockCancellationSignal(true), - mockCancellationSignal(false), - mockCancellationSignal(true), - mockCancellationSignal(false) - ), - CallData( - null, - null, - null, - null + val signals = listOf>( // + CallData( // + mockCancellationSignal(true), // + mockCancellationSignal(false), // + mockCancellationSignal(true), // + mockCancellationSignal(false) // + ), CallData( // + null, // + null, // + null, // + null // ) ) return queries.cartesianProductTwo(signals).map { it.toList() }.toArgumentsStream() } -fun sourceForTestInsert(): Stream> = sequenceOf( +fun sourceForTestInsert(): Stream> = sequenceOf( // //The ContentValues in this dataset always have the following order and counts: //String [0,2], null[0,1], Int[0,1] //This makes the ordered verification a lot easier - CallDataTwo( - mockContentValues("key1" to null), - mockContentValues("key2" to null), - "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", - "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1" to "value1"), - mockContentValues("key2" to "value2"), - "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", - "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1-1" to "value1-1", "key1-2" to "value1-2"), - mockContentValues("key2-1" to "value2-1", "key2-2" to "value2-2"), - "INSERT OR ROLLBACK INTO id_test(key1-1,key1-2) VALUES (?,?)", - "INSERT OR ABORT INTO comment_test(key2-1,key2-2) VALUES (?,?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1" to "value1", "intKey1" to 10101), - mockContentValues("key2" to "value2", "intKey2" to 20202), - "INSERT OR ROLLBACK INTO id_test(key1,intKey1) VALUES (?,?)", - "INSERT OR ABORT INTO comment_test(key2,intKey2) VALUES (?,?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1" to "value1", "nullKey1" to null), - mockContentValues("key2" to "value2", "nullKey2" to null), - "INSERT OR ROLLBACK INTO id_test(key1,nullKey1) VALUES (?,?)", - "INSERT OR ABORT INTO comment_test(key2,nullKey2) VALUES (?,?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1" to "value1", "nullKey1" to null, "intKey1" to 10101), - mockContentValues("key2" to "value2"), - "INSERT OR ROLLBACK INTO id_test(key1,nullKey1,intKey1) VALUES (?,?,?)", - "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" - ), - CallDataTwo( - mockContentValues("key1" to "value1"), - mockContentValues("key2" to "value2", "nullKey2" to null, "intKey2" to 20202), - "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", - "INSERT OR ABORT INTO comment_test(key2,nullKey2,intKey2) VALUES (?,?,?) -- Comment!" + CallDataTwo( // + mockContentValues("key1" to null), // + mockContentValues("key2" to null), // + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", // + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1" to "value1"), // + mockContentValues("key2" to "value2"), // + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", // + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1-1" to "value1-1", "key1-2" to "value1-2"), // + mockContentValues("key2-1" to "value2-1", "key2-2" to "value2-2"), // + "INSERT OR ROLLBACK INTO id_test(key1-1,key1-2) VALUES (?,?)", // + "INSERT OR ABORT INTO comment_test(key2-1,key2-2) VALUES (?,?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1" to "value1", "intKey1" to 10101), // + mockContentValues("key2" to "value2", "intKey2" to 20202), // + "INSERT OR ROLLBACK INTO id_test(key1,intKey1) VALUES (?,?)", // + "INSERT OR ABORT INTO comment_test(key2,intKey2) VALUES (?,?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1" to "value1", "nullKey1" to null), // + mockContentValues("key2" to "value2", "nullKey2" to null), // + "INSERT OR ROLLBACK INTO id_test(key1,nullKey1) VALUES (?,?)", // + "INSERT OR ABORT INTO comment_test(key2,nullKey2) VALUES (?,?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1" to "value1", "nullKey1" to null, "intKey1" to 10101), // + mockContentValues("key2" to "value2"), // + "INSERT OR ROLLBACK INTO id_test(key1,nullKey1,intKey1) VALUES (?,?,?)", // + "INSERT OR ABORT INTO comment_test(key2) VALUES (?) -- Comment!" // + ), CallDataTwo( // + mockContentValues("key1" to "value1"), // + mockContentValues("key2" to "value2", "nullKey2" to null, "intKey2" to 20202), // + "INSERT OR ROLLBACK INTO id_test(key1) VALUES (?)", // + "INSERT OR ABORT INTO comment_test(key2,nullKey2,intKey2) VALUES (?,?,?) -- Comment!" // ) ).asStream() -fun sourceForTestInsertConflictAlgorithms(): Stream> = sequenceOf( - Triple( - SQLiteDatabase.CONFLICT_NONE, - "INSERT INTO id_test(col1) VALUES (?)", - "INSERT INTO comment_test(col2) VALUES (?) -- Comment!" - ), - Triple( - SQLiteDatabase.CONFLICT_ROLLBACK, - "INSERT OR ROLLBACK INTO id_test(col1) VALUES (?)", - "INSERT OR ROLLBACK INTO comment_test(col2) VALUES (?) -- Comment!" - ), - Triple( - SQLiteDatabase.CONFLICT_ABORT, - "INSERT OR ABORT INTO id_test(col1) VALUES (?)", - "INSERT OR ABORT INTO comment_test(col2) VALUES (?) -- Comment!" - ), - Triple( - SQLiteDatabase.CONFLICT_FAIL, - "INSERT OR FAIL INTO id_test(col1) VALUES (?)", - "INSERT OR FAIL INTO comment_test(col2) VALUES (?) -- Comment!" - ), - Triple( - SQLiteDatabase.CONFLICT_IGNORE, - "INSERT OR IGNORE INTO id_test(col1) VALUES (?)", - "INSERT OR IGNORE INTO comment_test(col2) VALUES (?) -- Comment!" - ), - Triple( - SQLiteDatabase.CONFLICT_REPLACE, - "INSERT OR REPLACE INTO id_test(col1) VALUES (?)", - "INSERT OR REPLACE INTO comment_test(col2) VALUES (?) -- Comment!" - ), +fun sourceForTestInsertConflictAlgorithms(): Stream> = sequenceOf( // + Triple( // + SQLiteDatabase.CONFLICT_NONE, // + "INSERT INTO id_test(col1) VALUES (?)", // + "INSERT INTO comment_test(col2) VALUES (?) -- Comment!" // + ), Triple( // + SQLiteDatabase.CONFLICT_ROLLBACK, // + "INSERT OR ROLLBACK INTO id_test(col1) VALUES (?)", // + "INSERT OR ROLLBACK INTO comment_test(col2) VALUES (?) -- Comment!" // + ), Triple( // + SQLiteDatabase.CONFLICT_ABORT, // + "INSERT OR ABORT INTO id_test(col1) VALUES (?)", // + "INSERT OR ABORT INTO comment_test(col2) VALUES (?) -- Comment!" // + ), Triple( // + SQLiteDatabase.CONFLICT_FAIL, // + "INSERT OR FAIL INTO id_test(col1) VALUES (?)", // + "INSERT OR FAIL INTO comment_test(col2) VALUES (?) -- Comment!" // + ), Triple( // + SQLiteDatabase.CONFLICT_IGNORE, // + "INSERT OR IGNORE INTO id_test(col1) VALUES (?)", // + "INSERT OR IGNORE INTO comment_test(col2) VALUES (?) -- Comment!" // + ), Triple( // + SQLiteDatabase.CONFLICT_REPLACE, // + "INSERT OR REPLACE INTO id_test(col1) VALUES (?)", // + "INSERT OR REPLACE INTO comment_test(col2) VALUES (?) -- Comment!" // + ) ).asStream() fun sourceForTestUpdate(): Stream { - val contentValues = sequenceOf( - CallData( - mockContentValues("key1" to "value1"), - mockContentValues("key2" to "value2"), - mockContentValues("key1" to "value1"), - mockContentValues("key2" to "value2") - ), - CallData( - mockContentValues("key1" to null), - mockContentValues(), - mockContentValues("key1" to null), - mockContentValues() + val contentValues = sequenceOf( // + CallData( // + mockContentValues("key1" to "value1"), // + mockContentValues("key2" to "value2"), // + mockContentValues("key1" to "value1"), // + mockContentValues("key2" to "value2") // + ), CallData( // + mockContentValues("key1" to null), // + mockContentValues(), // + mockContentValues("key1" to null), // + mockContentValues() // ) ) - val whereClauses = listOf>( - CallData( - "`col1` = ?", - "`col2` = ?", - "`col1` = ?", - "`col2` = ? -- Comment!" - ), - CallData( - null, - null, - null, - "1 = 1 -- Comment!" + val whereClauses = listOf>( // + CallData( // + "`col1` = ?", // + "`col2` = ?", // + "`col1` = ?", // + "`col2` = ? -- Comment!" // + ), CallData( // + null, // + null, // + null, // + "1 = 1 -- Comment!" // ) ) val whereArgs = listOf?>>( //Use List instead of Array to make result data more readable - CallData( - listOf(), - null, - listOf(), - null - ), - CallData( - listOf("val1"), - listOf("val2"), - listOf("val1"), - listOf("val2") + CallData( // + listOf(), // + null, // + listOf(), // + null // + ), CallData( // + listOf("val1"), // + listOf("val2"), // + listOf("val1"), // + listOf("val2") // ) ) From a411e4520a896791019c8a384ec5210f931c590b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 20 Jun 2024 23:24:20 +0200 Subject: [PATCH 096/120] Refactored calls to "runMigrationsAndValidate" in "UpgradeDatabaseTest" Removed TODOs Added delegating method with closing logic and default arguments --- .../data/db/UpgradeDatabaseTest.kt | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index d10f60c28..493a2cccc 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -2,6 +2,7 @@ package org.cryptomator.data.db import android.content.Context import android.database.Cursor +import androidx.room.migration.Migration import androidx.room.testing.MigrationTestHelper import androidx.room.util.copyAndClose import androidx.sqlite.db.SupportSQLiteDatabase @@ -43,6 +44,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import java.io.IOException private const val TEST_DB = "migration-test" private const val LATEST_LEGACY_MIGRATION = 12 @@ -122,11 +124,14 @@ class UpgradeDatabaseTest { Upgrade11To12(sharedPreferencesHandler).migrate(db) db.close() - helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()) - helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()) //TODO Use correctly - //TODO Verify content (e.g. by using "loadAll") + runMigrationsAndValidate(13, Migration12To13()) + runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()) } + @Throws(IOException::class) + private fun runMigrationsAndValidate(version: Int, vararg migrations: Migration): SupportSQLiteDatabase { + return helper.runMigrationsAndValidate(TEST_DB, version, true, *migrations).also { db -> helper.closeWhenFinished(db) } + } @Test fun upgrade2To3() { @@ -718,12 +723,12 @@ class UpgradeDatabaseTest { assertTrue("Expected \".*$pre13Expected.*\", got \"$pre13Statement\"", pre13Statement.contains(pre13Expected)) db.close() - helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> val statement = referencesStatement(migratedDb) assertEquals(pre13Statement, statement) } - helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> val statement = referencesStatement(migratedDb) val expected = "FOREIGN KEY(folderCloudId) REFERENCES CLOUD_ENTITY(id) ON" assertTrue("Expected \".*$expected.*\", got \"$statement\"", statement.contains(expected)) @@ -768,12 +773,12 @@ class UpgradeDatabaseTest { assertIsUUID(pre13Statement.substring(pre13Statement.length - UUID_LENGTH)) db.close() - helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> val statement = indexStatement(migratedDb) assertEquals(pre13Statement, statement) } - helper.runMigrationsAndValidate(TEST_DB, 14, true, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> val statement = indexStatement(migratedDb) val expected = "CREATE UNIQUE INDEX `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `VAULT_ENTITY` (`folderPath` ASC, `folderCloudId` ASC)" assertEquals(expected, statement) @@ -814,7 +819,7 @@ class UpgradeDatabaseTest { } db.close() - helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) assertEquals(13, migratedDb.version) @@ -893,7 +898,7 @@ class UpgradeDatabaseTest { } db.close() - helper.runMigrationsAndValidate(TEST_DB, 13, true, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) assertEquals(13, migratedDb.version) @@ -914,10 +919,8 @@ class UpgradeDatabaseTest { fun migrate1To13WithRoom() { db.version = 1 db.close() - helper.runMigrationsAndValidate( - TEST_DB, + runMigrationsAndValidate( 13, - true, Upgrade1To2(), Upgrade2To3(context), Upgrade3To4(), From 1406d5de8db6e0d243748d3e0698993b70f56e36 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 21 Jun 2024 03:48:34 +0200 Subject: [PATCH 097/120] Upgraded room to version 2.6.1 Added room-grade-plugin to build Moved appropriate configuration to room-grade-plugin --- build.gradle | 2 ++ buildsystem/dependencies.gradle | 2 -- data/build.gradle | 29 +++++------------------------ 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index 4068d6139..5a43f3b74 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,14 @@ apply from: 'buildsystem/dependencies.gradle' buildscript { ext.kotlin_version = '1.9.24' + ext.roomVersion = '2.6.1' repositories { mavenCentral() google() } dependencies { classpath 'com.android.tools.build:gradle:8.4.1' + classpath "androidx.room:room-gradle-plugin:$roomVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1" } diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 75d1ad723..21ce78865 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -77,8 +77,6 @@ ext { lruFileCacheVersion = '1.2' - roomVersion = '2.5.2' - // cloud provider libs cryptolibVersion = '2.1.2' diff --git a/data/build.gradle b/data/build.gradle index d39c31da9..be4847ea3 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -1,3 +1,4 @@ +apply plugin: 'androidx.room' apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' @@ -24,14 +25,6 @@ android { buildConfigField "String", "VERSION_NAME", "\"${globalConfiguration["androidVersionName"]}\"" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - - javaCompileOptions { - annotationProcessorOptions { - compilerArgumentProviders( - new RoomSchemaArgProvider(roomSchemasDir) - ) - } - } } compileOptions { @@ -117,6 +110,10 @@ android { namespace 'org.cryptomator.data' } +room { + schemaDirectory(roomSchemasDir.path) +} + configurations.all { // Check for updates every build (use for cryptolib snapshot) //resolutionStrategy.cacheChangingModulesFor 0, 'seconds' @@ -266,20 +263,4 @@ tasks.withType(Test) { showStandardStreams = false } -} - -class RoomSchemaArgProvider implements CommandLineArgumentProvider { - - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - File schemaDir - - RoomSchemaArgProvider(File schemaDir) { - this.schemaDir = schemaDir - } - - @Override - Iterable asArguments() { - return ["-Aroom.schemaLocation=${schemaDir.path}".toString()] - } } \ No newline at end of file From c039e0ac53a9e234b884506698acce6925d8eb35 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 23 Jun 2024 02:44:40 +0200 Subject: [PATCH 098/120] Refactored bindings in "MappingSupportSQLiteStatement" Swapped bindings storage from lambdas to actual values Added additional copy-operation to ByteArray-bindings to prevent downstream changes Added utility methods *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../cryptomator/data/db/sqlmapping/Helpers.kt | 13 ++++++++ .../MappingSupportSQLiteDatabase.kt | 33 +++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt index 8c55d254f..dc462958f 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/Helpers.kt @@ -9,4 +9,17 @@ internal fun ContentValues.compatIsEmpty(): Boolean { } else { size() == 0 } +} + +internal fun MutableList.setLeniently(index: Int, element: E?): E? { + val limit = index - size + for (i in 0..limit) { //The size of the range is 0 if limit < 0, 1 if limit == 0 and limit + 1 else. + add(null) + } + return set(index, element) +} + +internal inline fun Sequence.toArray(size: Int): Array { + val iterator = iterator() + return Array(size) { iterator.next() } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 38f99603f..1e9e5786e 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -106,29 +106,33 @@ internal class MappingSupportSQLiteDatabase( private val sql: String ) : SupportSQLiteStatement { - private val bindings = mutableListOf<(SupportSQLiteStatement) -> Unit>() + private val bindings = mutableListOf() + + private fun saveBinding(index: Int, value: Any?): Any? { + return bindings.setLeniently(index - 1, value) + } override fun bindBlob(index: Int, value: ByteArray) { - bindings.add { statement -> statement.bindBlob(index, value.copyOf()) } + saveBinding(index, value.copyOf()) } override fun bindDouble(index: Int, value: Double) { - bindings.add { statement -> statement.bindDouble(index, value) } + saveBinding(index, value) } override fun bindLong(index: Int, value: Long) { - bindings.add { statement -> statement.bindLong(index, value) } + saveBinding(index, value) } override fun bindNull(index: Int) { - bindings.add { statement -> statement.bindNull(index) } + saveBinding(index, null) } override fun bindString(index: Int, value: String) { - bindings.add { statement -> statement.bindString(index, value) } + saveBinding(index, value) } - override fun clearBindings() { + override fun clearBindings(): Unit { bindings.clear() } @@ -159,9 +163,18 @@ internal class MappingSupportSQLiteDatabase( @VisibleForTesting internal fun newBoundStatement(): SupportSQLiteStatement { return delegate.compileStatement(map(sql)).also { statement -> - for (binding: (SupportSQLiteStatement) -> Unit in bindings) { - binding(statement) - } + SimpleSQLiteQuery.bind(statement, prepareBindArgs()) + } + } + + private fun prepareBindArgs(): Array { + return bindings.asSequence().map { binding -> prepareSingleBindArg(binding) }.toArray(bindings.size) + } + + private fun prepareSingleBindArg(binding: Any?): Any? { + return when (binding) { + is ByteArray -> binding.copyOf() + else -> binding } } } From 558856362faf3b3193f9a92acac1f40845086e8d Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:02:41 +0200 Subject: [PATCH 099/120] Made "MappingSupportSQLiteStatement" threadsafe *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../db/sqlmapping/MappingSupportSQLiteDatabase.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 1e9e5786e..addf85443 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -108,8 +108,8 @@ internal class MappingSupportSQLiteDatabase( private val bindings = mutableListOf() - private fun saveBinding(index: Int, value: Any?): Any? { - return bindings.setLeniently(index - 1, value) + private fun saveBinding(index: Int, value: Any?): Any? = synchronized(bindings) { + return@synchronized bindings.setLeniently(index - 1, value) } override fun bindBlob(index: Int, value: ByteArray) { @@ -132,8 +132,8 @@ internal class MappingSupportSQLiteDatabase( saveBinding(index, value) } - override fun clearBindings(): Unit { - bindings.clear() + override fun clearBindings(): Unit = synchronized(bindings) { + return@synchronized bindings.clear() } override fun close() { @@ -167,8 +167,8 @@ internal class MappingSupportSQLiteDatabase( } } - private fun prepareBindArgs(): Array { - return bindings.asSequence().map { binding -> prepareSingleBindArg(binding) }.toArray(bindings.size) + private fun prepareBindArgs(): Array = synchronized(bindings) { + return@synchronized bindings.asSequence().map { binding -> prepareSingleBindArg(binding) }.toArray(bindings.size) } private fun prepareSingleBindArg(binding: Any?): Any? { From df6c32bf9ba184643fce79df73e5f344a6539ff1 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:10:43 +0200 Subject: [PATCH 100/120] Refactored initialization of "DatabaseOpenHelperFactory" Moved call to "asCacheControlled" for singleton instance of "DatabaseOpenHelperFactory" from "DatabaseModule.provideInternalCryptomatorDatabase" to new binding "DatabaseModule.provideOpenHelperFactory" to prevent unnecessary duplicate invocations *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/org/cryptomator/data/db/DatabaseModule.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 2c20edba0..d3393c200 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -5,6 +5,7 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -57,7 +58,7 @@ class DatabaseModule { context: Context, // @DbInternal migrations: Array, // @DbInternal dbTemplateStreamCallable: Callable, // - openHelperFactory: DatabaseOpenHelperFactory, // + @DbInternal openHelperFactory: SupportSQLiteOpenHelper.Factory, // @DbInternal databaseName: String, // ): CryptomatorDatabase { LOG.i("Building database (target version: %s)", CRYPTOMATOR_DATABASE_VERSION) @@ -66,7 +67,7 @@ class DatabaseModule { .createFromInputStream(dbTemplateStreamCallable) // .addMigrations(*migrations) // .addCallback(DatabaseCallback) // - .openHelperFactory(openHelperFactory.asCacheControlled()) // + .openHelperFactory(openHelperFactory) // .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // .fallbackToDestructiveMigrationFrom(0) // .build() //Fails if no migration is found (especially when downgrading) @@ -96,6 +97,13 @@ class DatabaseModule { return templateFactory.create().templateFile() } + @Singleton + @Provides + @DbInternal + internal fun provideOpenHelperFactory(openHelperFactory: DatabaseOpenHelperFactory): SupportSQLiteOpenHelper.Factory { + return openHelperFactory.asCacheControlled() + } + @Singleton @Provides @DbInternal From 818bac6d412a8cce72c78175320138b168d31745 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:06:06 +0200 Subject: [PATCH 101/120] Refactored initialization of database template Added logic to delete templates after use Improved template verification Changed binding type of templates from singleton to single-use Changed type of exposed binding to "InputStream" Updated "TemplateDatabaseContextTest", "CorruptedDatabaseTest", "CreateDatabaseTest" and "UpgradeDatabaseTest" --- .../data/db/CorruptedDatabaseTest.kt | 10 +-- .../cryptomator/data/db/CreateDatabaseTest.kt | 40 +++++++++-- .../data/db/UpgradeDatabaseTest.kt | 11 +-- .../templating/TemplateDatabaseContextTest.kt | 58 +++++++++++++--- .../org/cryptomator/data/db/DatabaseModule.kt | 26 ++++--- .../data/db/templating/DbTemplateComponent.kt | 4 +- .../data/db/templating/DbTemplateModule.kt | 68 +++++++++++++++---- 7 files changed, 171 insertions(+), 46 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index b91663123..0dc34b201 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -39,10 +39,11 @@ class CorruptedDatabaseTest { private val context = InstrumentationRegistry.getInstrumentation().context private val sharedPreferencesHandler = SharedPreferencesHandler(context) private val openHelperFactory = DatabaseOpenHelperFactory { throw IllegalStateException() } - private val templateDbFile = DbTemplateModule().let { - it.provideDbTemplateFile(it.provideConfiguration(TemplateDatabaseContext(context))) + private val templateDbStream = DbTemplateModule().let { + it.provideDbTemplateStream(it.provideConfiguration(TemplateDatabaseContext(context))) }.also { - it.deleteOnExit() + require(it.markSupported()) + it.mark(it.available()) } @Before @@ -59,6 +60,7 @@ class CorruptedDatabaseTest { @After fun tearDown() { context.getDatabasePath(TEST_DB).delete() + templateDbStream.reset() } @Test @@ -83,7 +85,7 @@ class CorruptedDatabaseTest { databaseModule.provideInternalCryptomatorDatabase( context, migrations, - databaseModule.provideDbTemplateStreamCallable { templateDbFile }, + { templateDbStream }, openHelperFactory, TEST_DB ).useFinally({ db -> diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index b91d25626..7164fffc8 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -11,10 +11,16 @@ import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail +import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith +import java.nio.file.Files @RunWith(AndroidJUnit4::class) @SmallTest @@ -22,11 +28,17 @@ class CreateDatabaseTest { private val context = InstrumentationRegistry.getInstrumentation().context + @get:Rule + val tempFolder: TemporaryFolder = TemporaryFolder() + @Test - fun testProvideDbTemplateFile() { - val templateDatabaseContext = TemplateDatabaseContext(context) - val templateFile = DbTemplateModule().let { it.provideDbTemplateFile(it.provideConfiguration(templateDatabaseContext)) } - assertTrue(templateFile.exists()) + fun testProvideDbTemplateStream() { + val templateStream = DbTemplateModule().let { it.provideDbTemplateStream(it.provideConfiguration(TemplateDatabaseContext(context))) } + val templateFile = tempFolder.newFolder("provideDbTemplateStream").resolve(DATABASE_NAME) + Files.copy(templateStream, templateFile.toPath()) + val templateDatabaseContext = TemplateDatabaseContext(context).also { + it.templateFile = templateFile + } val templateDb = SupportSQLiteOpenHelper.Configuration(templateDatabaseContext, DATABASE_NAME, object : SupportSQLiteOpenHelper.Callback(1) { override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") @@ -47,4 +59,24 @@ class CreateDatabaseTest { assertTrue("Missing element(s): ${elements.joinToString(prefix = "\"", postfix = "\"")}", elements.isEmpty()) } } + + @Test + fun testProvideDbTemplateStreamFiles() { + val templateDatabaseContext = TemplateDatabaseContext(context) + assertNull(templateDatabaseContext.templateFile) + DbTemplateModule().let { it.provideDbTemplateStream(it.provideConfiguration(templateDatabaseContext)) }.close() + + val templateFile = assertNotNullObj(templateDatabaseContext.templateFile) + assertFalse(templateFile.exists()) + + val parentDir = assertNotNullObj(templateFile.parentFile) + assertFalse(parentDir.exists()) + assertEquals(requireNotNull(context.cacheDir), parentDir.parentFile) + } +} + +private fun assertNotNullObj(obj: T?): T { + //TODO Improve this method by using the kotlin contract API once it's stable + assertNotNull(obj) + return obj!! } \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 493a2cccc..622d04261 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -45,6 +45,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.io.IOException +import java.nio.file.Files private const val TEST_DB = "migration-test" private const val LATEST_LEGACY_MIGRATION = 12 @@ -59,10 +60,11 @@ class UpgradeDatabaseTest { private val context = instrumentation.context private val sharedPreferencesHandler = SharedPreferencesHandler(context) - private val templateDbFile = DbTemplateModule().let { - it.provideDbTemplateFile(it.provideConfiguration(TemplateDatabaseContext(context))) + private val templateDbStream = DbTemplateModule().let { + it.provideDbTemplateStream(it.provideConfiguration(TemplateDatabaseContext(context))) }.also { - it.deleteOnExit() + require(it.markSupported()) + it.mark(it.available()) } private lateinit var db: SupportSQLiteDatabase @@ -83,7 +85,7 @@ class UpgradeDatabaseTest { println("Test database \"${dbFile.absolutePath}\" not cleaned up. Deleting...") dbFile.delete() } - templateDbFile.copyTo(dbFile) + Files.copy(templateDbStream, dbFile.toPath()) } db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { @@ -107,6 +109,7 @@ class UpgradeDatabaseTest { db.close() //Room handles creating/deleting room-only databases correctly, but this falls apart when using the FrameworkSQLiteOpenHelper directly context.getDatabasePath(TEST_DB).delete() + templateDbStream.reset() } @Test diff --git a/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt index 101004727..300ffaea7 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/templating/TemplateDatabaseContextTest.kt @@ -4,11 +4,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import org.cryptomator.data.db.DATABASE_NAME -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Test import org.junit.runner.RunWith +import java.io.File @RunWith(AndroidJUnit4::class) @SmallTest @@ -21,16 +21,54 @@ class TemplateDatabaseContextTest { TemplateDatabaseContext(baseContext).getDatabasePath("Database42") } + @Test(expected = IllegalArgumentException::class) + fun testTempDatabaseContextIllegalNameFileSet() { + val templateDbContext = TemplateDatabaseContext(baseContext) + templateDbContext.templateFile = File("/test/12345/Database") + templateDbContext.getDatabasePath("Database42") + } + + @Test + fun testTempDatabaseContextTemplateFileNotSet() { + val templateDbContext = TemplateDatabaseContext(baseContext) + assertNull(templateDbContext.templateFile) + } + + @Test(expected = IllegalArgumentException::class) + fun testTempDatabaseContextTemplateFileNotSetThrows() { + val templateDbContext = TemplateDatabaseContext(baseContext) + templateDbContext.getDatabasePath(DATABASE_NAME) + } + + @Test(expected = IllegalArgumentException::class) + fun testTempDatabaseContextFileSetTwice() { + val templateDbContext = TemplateDatabaseContext(baseContext) + assertNull(templateDbContext.templateFile) + + val actualTemplateFile = File("/test/12345/Database") + templateDbContext.templateFile = actualTemplateFile + assertSame(actualTemplateFile, templateDbContext.templateFile) + + templateDbContext.templateFile = File("/test/67890/Throws") + } + + @Test(expected = IllegalArgumentException::class) + fun testTempDatabaseContextFileSetWithNull() { + val templateDbContext = TemplateDatabaseContext(baseContext) + templateDbContext.templateFile = null + } + @Test fun testTempDatabaseContext() { val templateDbContext = TemplateDatabaseContext(baseContext) - val templatePath = templateDbContext.getDatabasePath(DATABASE_NAME) - templatePath.parentFile.let { tempDir -> - assertNotNull(tempDir) - assertEquals(baseContext.cacheDir, tempDir!!.parentFile) - } - - val secondInvocation = templateDbContext.getDatabasePath(DATABASE_NAME) - assertSame(templatePath, secondInvocation) + assertNull(templateDbContext.templateFile) + + val actualTemplateFile = File("/test/12345/Database") + templateDbContext.templateFile = actualTemplateFile + + val invocation1 = templateDbContext.getDatabasePath(DATABASE_NAME) + assertSame(actualTemplateFile, invocation1) + val invocation2 = templateDbContext.getDatabasePath(DATABASE_NAME) + assertSame(actualTemplateFile, invocation2) } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index d3393c200..3dba945d5 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -23,13 +23,13 @@ import org.cryptomator.data.db.templating.DbTemplateComponent import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named import java.io.File +import java.io.IOException import java.io.InputStream import java.util.concurrent.Callable import javax.inject.Named import javax.inject.Provider import javax.inject.Qualifier import javax.inject.Singleton -import dagger.Lazy import dagger.Module import dagger.Provides import timber.log.Timber @@ -85,16 +85,22 @@ class DatabaseModule { @Singleton @Provides @DbInternal - fun provideDbTemplateStreamCallable(@DbInternal dbTemplateFile: Lazy): Callable = Callable { + fun provideDbTemplateStreamCallable(templateFactory: DbTemplateComponent.Factory): Callable = Callable { LOG.d("Creating database template stream") - return@Callable dbTemplateFile.get().inputStream() - } - - @Singleton - @Provides - @DbInternal - fun provideDbTemplateFile(templateFactory: DbTemplateComponent.Factory): File { - return templateFactory.create().templateFile() + try { + return@Callable templateFactory.create().templateStream() + } catch (t: Throwable) { + if (t !is IOException) { + throw t + } + LOG.w(t, "IOException while reading database template, retrying...") + try { + return@Callable templateFactory.create().templateStream() + } catch (tInner: Throwable) { + tInner.addSuppressed(t) + throw tInner + } + } } @Singleton diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt index 96f341e34..44131756b 100644 --- a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateComponent.kt @@ -1,6 +1,6 @@ package org.cryptomator.data.db.templating -import java.io.File +import java.io.InputStream import dagger.Subcomponent @DbTemplateScoped @@ -8,7 +8,7 @@ import dagger.Subcomponent interface DbTemplateComponent { @DbTemplateScoped - fun templateFile(): File + fun templateStream(): InputStream @Subcomponent.Factory interface Factory { diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt index 170e80444..77ef4d7a2 100644 --- a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt @@ -8,13 +8,21 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import org.cryptomator.data.db.DATABASE_NAME import org.cryptomator.data.db.applyDefaultConfiguration import org.cryptomator.data.db.migrations.legacy.Upgrade0To1 +import org.cryptomator.data.util.useFinally import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named import java.io.File +import java.io.InputStream import java.nio.file.Files +import java.nio.file.LinkOption +import java.nio.file.Path import javax.inject.Inject import dagger.Module import dagger.Provides +import kotlin.io.path.Path +import kotlin.io.path.fileSize +import kotlin.io.path.isRegularFile +import kotlin.io.path.readBytes import timber.log.Timber private val LOG = Timber.Forest.named("DbTemplateModule") @@ -24,16 +32,38 @@ class DbTemplateModule { @DbTemplateScoped @Provides - internal fun provideDbTemplateFile(configuration: SupportSQLiteOpenHelper.Configuration): File { + internal fun provideDbTemplateStream(configuration: SupportSQLiteOpenHelper.Configuration): InputStream { LOG.d("Creating database template file") ThreadUtil.assumeNotMainThread() - return FrameworkSQLiteOpenHelperFactory().create(configuration).use { - initDatabase(it) - }.let { - require(it != null && it != ":memory:") { "Template database must not be in-memory" } - LOG.d("Created database template file") - File(it) - } + + val templateDatabaseContext = configuration.context + require(templateDatabaseContext is TemplateDatabaseContext) + var parentDir: Path? = null + return useFinally({ + parentDir = Files.createTempDirectory(configuration.context.cacheDir.toPath(), "DbTemplate") + val templateFile: Path = parentDir!!.resolve(DATABASE_NAME) + templateDatabaseContext.templateFile = templateFile.toFile() + + val initializedPath = FrameworkSQLiteOpenHelperFactory().create(configuration).use { initDatabase(it) } + verifyTemplate(templateFile, Path(requireNotNull(initializedPath))) + + val length: Long = templateFile.fileSize() + require(length > 0L && length < Int.MAX_VALUE.toLong()) { "Template database file must be readable and smaller than 2 GB; template file at \"$templateFile\" is $length B long" } + LOG.d("Created database template file ($length B) at \"$templateFile\"") + + //If this method throws an OutOfMemoryError, the db template mostly likely was larger than 2GB + return@useFinally templateFile.readBytes().inputStream().also { + LOG.d("Created database template stream (${it.available()} B) from \"$templateFile\"") + } + }, finallyBlock = { + try { + if (parentDir?.toFile()?.deleteRecursively() == false) { + LOG.w("Failed to clean up template database file in \"$parentDir\"") + } + } catch (e: Exception) { + LOG.e(e, "Exception while cleaning up template database file in \"$parentDir\"") + } + }) } private fun initDatabase(openHelper: SupportSQLiteOpenHelper): String? { @@ -44,6 +74,14 @@ class DbTemplateModule { } } + private fun verifyTemplate(templateFile: Path, initializedPath: Path) { + require(Files.isSameFile(templateFile, initializedPath)) + require(templateFile.isRegularFile(LinkOption.NOFOLLOW_LINKS)) + if (templateFile != initializedPath) { + LOG.i("\"$templateFile\" was initialized at different path (\"$initializedPath\")") + } + } + @DbTemplateScoped @Provides internal fun provideConfiguration(templateDatabaseContext: TemplateDatabaseContext): SupportSQLiteOpenHelper.Configuration { @@ -68,12 +106,18 @@ class DbTemplateModule { @DbTemplateScoped internal class TemplateDatabaseContext @Inject constructor(context: Context) : ContextWrapper(context) { - private val dbFile: File by lazy { - return@lazy Files.createTempDirectory(cacheDir.toPath(), "DbTemplate").resolve(DATABASE_NAME).toFile() - } + internal var templateFile: File? + get() = _templateFile + set(value) { + require(_templateFile == null) + requireNotNull(value) + _templateFile = value + } + + private var _templateFile: File? = null override fun getDatabasePath(name: String?): File { require(name == DATABASE_NAME) - return dbFile + return requireNotNull(templateFile) { "Template file should be set by \"provideDbTemplateStream\"" } } } \ No newline at end of file From 5c37c12975b5a437751843bb2dbe42e11f4887f6 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:14:14 +0200 Subject: [PATCH 102/120] Standardized database configuration Refactored database configuration in "CreateDatabaseTest" and "UpgradeDatabaseTest" to follow standard laid out by production logic/other tests --- .../cryptomator/data/db/CreateDatabaseTest.kt | 12 +++++--- .../data/db/UpgradeDatabaseTest.kt | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index 7164fffc8..d86ff136a 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -40,10 +40,14 @@ class CreateDatabaseTest { it.templateFile = templateFile } - val templateDb = SupportSQLiteOpenHelper.Configuration(templateDatabaseContext, DATABASE_NAME, object : SupportSQLiteOpenHelper.Callback(1) { - override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = fail("Database should already be target version") - }).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase } + val config = SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // + .name(DATABASE_NAME) // + .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = fail("Database should already be target version") + }).build() + + val templateDb = FrameworkSQLiteOpenHelperFactory().create(config).writableDatabase assertEquals(1, templateDb.version) val elements = mutableListOf("CLOUD_ENTITY", "VAULT_ENTITY", "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 622d04261..e10a48ef7 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -88,20 +88,24 @@ class UpgradeDatabaseTest { Files.copy(templateDbStream, dbFile.toPath()) } - db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB, object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { - override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = false // - ) + val config = SupportSQLiteOpenHelper.Configuration.builder(context) // + .name(TEST_DB) // + .callback(object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = false // + ) + + override fun onCreate(db: SupportSQLiteDatabase) { + fail("Database should not be created, but copied from template") + } - override fun onCreate(db: SupportSQLiteDatabase) { - fail("Database should not be created, but copied from template") - } + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + assertEquals(1, oldVersion) + assertEquals(LATEST_LEGACY_MIGRATION, newVersion) + } + }).build() - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - assertEquals(1, oldVersion) - assertEquals(LATEST_LEGACY_MIGRATION, newVersion) - } - }).let { FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(it).writableDatabase } + db = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(config).writableDatabase } @After From f496fe1b6a5788a90f04b440199c12f0722e615e Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:02:42 +0200 Subject: [PATCH 103/120] Standardized database closing Refactored "CreateDatabaseTest" and "UpgradeDatabaseTest" to close databases following the standard laid out by production logic/other tests --- .../cryptomator/data/db/CreateDatabaseTest.kt | 33 +++++++++++-------- .../data/db/UpgradeDatabaseTest.kt | 5 ++- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index d86ff136a..153958d1d 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -47,21 +47,28 @@ class CreateDatabaseTest { override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = fail("Database should already be target version") }).build() - val templateDb = FrameworkSQLiteOpenHelperFactory().create(config).writableDatabase - assertEquals(1, templateDb.version) + FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> + verifyDbTemplateStream(openHelper) + } + } - val elements = mutableListOf("CLOUD_ENTITY", "VAULT_ENTITY", "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") - Sql.query("sqlite_master") // - .columns(listOf("name")) // - .where("name", Sql.notEq("android_metadata")) // - .executeOn(templateDb) // - .useCursor { - while (it.moveToNext()) { - val elementName = it.getString(it.getColumnIndex("name")) - assertTrue("Unknown/Duplicate element: \"$elementName\"", elements.remove(elementName)) + private fun verifyDbTemplateStream(openHelper: SupportSQLiteOpenHelper) { + openHelper.writableDatabase.use { templateDb -> + assertEquals(1, templateDb.version) + + val elements = mutableListOf("CLOUD_ENTITY", "VAULT_ENTITY", "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") + Sql.query("sqlite_master") // + .columns(listOf("name")) // + .where("name", Sql.notEq("android_metadata")) // + .executeOn(templateDb) // + .useCursor { + while (it.moveToNext()) { + val elementName = it.getString(it.getColumnIndex("name")) + assertTrue("Unknown/Duplicate element: \"$elementName\"", elements.remove(elementName)) + } + assertTrue("Missing element(s): ${elements.joinToString(prefix = "\"", postfix = "\"")}", elements.isEmpty()) } - assertTrue("Missing element(s): ${elements.joinToString(prefix = "\"", postfix = "\"")}", elements.isEmpty()) - } + } } @Test diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index e10a48ef7..367220f55 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -67,6 +67,7 @@ class UpgradeDatabaseTest { it.mark(it.available()) } + private lateinit var openHelper: SupportSQLiteOpenHelper private lateinit var db: SupportSQLiteDatabase @get:Rule @@ -105,12 +106,14 @@ class UpgradeDatabaseTest { } }).build() - db = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(config).writableDatabase + openHelper = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(config) + db = openHelper.writableDatabase } @After fun tearDown() { db.close() + openHelper.close() //Room handles creating/deleting room-only databases correctly, but this falls apart when using the FrameworkSQLiteOpenHelper directly context.getDatabasePath(TEST_DB).delete() templateDbStream.reset() From 26bad8d83b64efcdbdef5be24927ad5d5745a63d Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:14:37 +0200 Subject: [PATCH 104/120] Added missing WAL configuration/verification *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/org/cryptomator/data/db/CreateDatabaseTest.kt | 4 ++++ .../java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt | 1 + 2 files changed, 5 insertions(+) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index 153958d1d..3714eb8bb 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -43,6 +43,10 @@ class CreateDatabaseTest { val config = SupportSQLiteOpenHelper.Configuration.builder(templateDatabaseContext) // .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + writeAheadLoggingEnabled = false // + ) + override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = fail("Database should already be target version") }).build() diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index a370d236c..ca1fac7ab 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -46,6 +46,7 @@ private class PatchedCallback( override fun onConfigure(db: SupportSQLiteDatabase) { LOG.d("Called onConfigure for \"${db.path}\"@${db.version}") + require(!db.isWriteAheadLoggingEnabled) { "WAL for \"${db.path}\" should already be disabled by room" } db.applyDefaultConfiguration( // writeAheadLoggingEnabled = null //WAL is handled by Room ) From 9abed180ea8e47e561f0602bc40bdc903e3a65e4 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:09:03 +0200 Subject: [PATCH 105/120] Refactored WAL initialization Moved WAL initialization from the "onConfigure" methods (via "applyDefaultConfiguration") of the database configurations to the corresponding instances of "SupportSQLiteOpenHelper" Updated extension method "applyDefaultConfiguration" for "SupportSQLiteDatabase" defined in "CryptomatorDatabase.kt" to no longer configure WAL but verify that it has been set properly in the corresponding "SupportSQLiteOpenHelper" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../java/org/cryptomator/data/db/CorruptedDatabaseTest.kt | 3 ++- .../java/org/cryptomator/data/db/CreateDatabaseTest.kt | 3 ++- .../java/org/cryptomator/data/db/UpgradeDatabaseTest.kt | 3 ++- .../java/org/cryptomator/data/db/CryptomatorDatabase.kt | 8 +++----- .../org/cryptomator/data/db/DatabaseOpenHelperFactory.kt | 2 +- .../cryptomator/data/db/templating/DbTemplateModule.kt | 7 +++++-- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 0dc34b201..1ff1b78ed 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -104,7 +104,7 @@ private fun createVersion0Database(context: Context, databaseName: String) { .name(databaseName) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = false // + assertedWalEnabledStatus = false // ) override fun onCreate(db: SupportSQLiteDatabase) = throw InterruptCreationException() @@ -112,6 +112,7 @@ private fun createVersion0Database(context: Context, databaseName: String) { }).build() FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> + openHelper.setWriteAheadLoggingEnabled(false) try { //The "use" block in "initVersion0Database" should not be reached, let alone finished; ... initVersion0Database(openHelper) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt index 3714eb8bb..287e31ea2 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CreateDatabaseTest.kt @@ -44,7 +44,7 @@ class CreateDatabaseTest { .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = false // + assertedWalEnabledStatus = false // ) override fun onCreate(db: SupportSQLiteDatabase) = fail("Database should already exist") @@ -52,6 +52,7 @@ class CreateDatabaseTest { }).build() FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> + openHelper.setWriteAheadLoggingEnabled(false) verifyDbTemplateStream(openHelper) } } diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 367220f55..23c896f1f 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -93,7 +93,7 @@ class UpgradeDatabaseTest { .name(TEST_DB) // .callback(object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = false // + assertedWalEnabledStatus = false // ) override fun onCreate(db: SupportSQLiteDatabase) { @@ -107,6 +107,7 @@ class UpgradeDatabaseTest { }).build() openHelper = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(config) + openHelper.setWriteAheadLoggingEnabled(false) db = openHelper.writableDatabase } diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index 943de5268..da904d803 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -170,11 +170,9 @@ private fun Array.uniqueToSet(): Set = toSet().also { require(this.size == it.size) { "Array contained ${this.size - it.size} duplicate elements." } } -fun SupportSQLiteDatabase.applyDefaultConfiguration(writeAheadLoggingEnabled: Boolean?) { - when (writeAheadLoggingEnabled) { - true -> enableWriteAheadLogging() - false -> disableWriteAheadLogging() - null -> {} +fun SupportSQLiteDatabase.applyDefaultConfiguration(assertedWalEnabledStatus: Boolean) { + require(isWriteAheadLoggingEnabled == assertedWalEnabledStatus) { + "Expected WAL enabled status to be $assertedWalEnabledStatus for \"${path}\", but was $isWriteAheadLoggingEnabled" } setForeignKeyConstraintsEnabled(true) } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt index ca1fac7ab..2177a0e09 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseOpenHelperFactory.kt @@ -48,7 +48,7 @@ private class PatchedCallback( LOG.d("Called onConfigure for \"${db.path}\"@${db.version}") require(!db.isWriteAheadLoggingEnabled) { "WAL for \"${db.path}\" should already be disabled by room" } db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = null //WAL is handled by Room + assertedWalEnabledStatus = false //WAL is handled by Room ) // delegateCallback.onConfigure(db) diff --git a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt index 77ef4d7a2..1061cbda0 100644 --- a/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/templating/DbTemplateModule.kt @@ -44,7 +44,10 @@ class DbTemplateModule { val templateFile: Path = parentDir!!.resolve(DATABASE_NAME) templateDatabaseContext.templateFile = templateFile.toFile() - val initializedPath = FrameworkSQLiteOpenHelperFactory().create(configuration).use { initDatabase(it) } + val initializedPath = FrameworkSQLiteOpenHelperFactory().create(configuration).use { openHelper -> + openHelper.setWriteAheadLoggingEnabled(false) + initDatabase(openHelper) + } verifyTemplate(templateFile, Path(requireNotNull(initializedPath))) val length: Long = templateFile.fileSize() @@ -89,7 +92,7 @@ class DbTemplateModule { .name(DATABASE_NAME) // .callback(object : SupportSQLiteOpenHelper.Callback(1) { override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - writeAheadLoggingEnabled = false // + assertedWalEnabledStatus = false // ) override fun onCreate(db: SupportSQLiteDatabase) { From f206740d384ce40eace33366ed1718888a805c67 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 26 Jun 2024 22:30:53 +0200 Subject: [PATCH 106/120] Performed cleanup Added comments and documentation Simplified implementation for "RandomUUIDMapping.mapWhereClause" Replaced import alias "org.mockito.Mockito.`when` as whenCalled" with built-in "org.mockito.kotlin.whenever" in "MappingSupportSQLiteDatabaseTest.kt" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/CorruptedDatabaseTest.kt | 4 +- .../cryptomator/data/db/SQLiteCacheControl.kt | 2 +- .../MappingSupportSQLiteDatabase.kt | 3 ++ .../MappingSupportSQLiteDatabaseTest.kt | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 1ff1b78ed..69cff3f34 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -78,7 +78,9 @@ class CorruptedDatabaseTest { Upgrade9To10(sharedPreferencesHandler), Upgrade10To11(), Upgrade11To12(sharedPreferencesHandler), - Migration12To13() + // + Migration12To13(), + //Auto: 13 -> 14 ) createVersion0Database(context, TEST_DB) diff --git a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt index 74a3de865..b0b230f24 100644 --- a/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt +++ b/data/src/main/java/org/cryptomator/data/db/SQLiteCacheControl.kt @@ -18,7 +18,7 @@ object SQLiteCacheControl { } override fun mapWhereClause(whereClause: String?): String { - return map(whereClause ?: "1 = 1") + return map(whereClause ?: "1") } override fun mapCursor(cursor: Cursor): Cursor { diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index addf85443..45640f4a7 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -226,6 +226,9 @@ fun SupportSQLiteOpenHelper.Factory.asMapped(mappingFunction: SQLMappingFunction return MappingSupportSQLiteOpenHelperFactory(this, mappingFunction) } +/** + * Implementations must be threadsafe. + */ interface SQLMappingFunction { fun map(sql: String): String diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 79f60d41a..1551bfbf1 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -1,7 +1,6 @@ package org.cryptomator.data.db.sqlmapping import org.mockito.ArgumentMatchers.argThat as defaultArgThat -import org.mockito.Mockito.`when` as whenCalled import org.mockito.kotlin.any as reifiedAny import org.mockito.kotlin.anyOrNull as reifiedAnyOrNull import org.mockito.kotlin.argThat as reifiedArgThat @@ -48,6 +47,7 @@ import org.mockito.kotlin.anyArray import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder import org.mockito.kotlin.isNull +import org.mockito.kotlin.whenever import org.mockito.stubbing.Answer import org.mockito.stubbing.OngoingStubbing import java.util.LinkedList @@ -101,7 +101,7 @@ class MappingSupportSQLiteDatabaseTest { @Test fun testQueryString() { - whenCalled(delegateMock.query(anyString())).thenReturn(DUMMY_CURSOR) + whenever(delegateMock.query(anyString())).thenReturn(DUMMY_CURSOR) identityMapping.query("SELECT `col` FROM `id_test`") commentMapping.query("SELECT `col` FROM `comment_test`") @@ -113,7 +113,7 @@ class MappingSupportSQLiteDatabaseTest { @Test fun testQueryStringWithBindings() { - whenCalled(delegateMock.query(anyString(), anyArray())).thenReturn(DUMMY_CURSOR) + whenever(delegateMock.query(anyString(), anyArray())).thenReturn(DUMMY_CURSOR) identityMapping.query("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")) commentMapping.query("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2")) @@ -126,7 +126,7 @@ class MappingSupportSQLiteDatabaseTest { @Test fun testQueryBindable() { - whenCalled(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) + whenever(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test`")) commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test`")) @@ -143,7 +143,7 @@ class MappingSupportSQLiteDatabaseTest { @Test fun testQueryBindableWithBindings() { - whenCalled(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) + whenever(delegateMock.query(reifiedAny())).thenReturn(DUMMY_CURSOR) identityMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1"))) commentMapping.query(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ?", arrayOf("test2"))) @@ -161,7 +161,7 @@ class MappingSupportSQLiteDatabaseTest { @ParameterizedTest @MethodSource("org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabaseTestKt#sourceForTestQueryCancelable") fun testQueryCancelable(queries: CallData, signals: CallData) { - whenCalled(delegateMock.query(reifiedAny(), reifiedAnyOrNull())).thenReturn(DUMMY_CURSOR) + whenever(delegateMock.query(reifiedAny(), reifiedAnyOrNull())).thenReturn(DUMMY_CURSOR) identityMapping.query(queries.idCall, signals.idCall) commentMapping.query(queries.commentCall, signals.commentCall) @@ -184,8 +184,8 @@ class MappingSupportSQLiteDatabaseTest { val (idCompiledStatement: SupportSQLiteStatement, idBindings: Map) = mockSupportSQLiteStatement() val (commentCompiledStatement: SupportSQLiteStatement, commentBindings: Map) = mockSupportSQLiteStatement() - whenCalled(delegateMock.compileStatement(arguments.idExpected)).thenReturn(idCompiledStatement) - whenCalled(delegateMock.compileStatement(arguments.commentExpected)).thenReturn(commentCompiledStatement) + whenever(delegateMock.compileStatement(arguments.idExpected)).thenReturn(idCompiledStatement) + whenever(delegateMock.compileStatement(arguments.commentExpected)).thenReturn(commentCompiledStatement) val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) @@ -232,8 +232,8 @@ class MappingSupportSQLiteDatabaseTest { val (idCompiledStatement: SupportSQLiteStatement, idBindings: Map) = mockSupportSQLiteStatement() val (commentCompiledStatement: SupportSQLiteStatement, commentBindings: Map) = mockSupportSQLiteStatement() - whenCalled(delegateMock.compileStatement(idStatement)).thenReturn(idCompiledStatement) - whenCalled(delegateMock.compileStatement(commentStatement)).thenReturn(commentCompiledStatement) + whenever(delegateMock.compileStatement(idStatement)).thenReturn(idCompiledStatement) + whenever(delegateMock.compileStatement(commentStatement)).thenReturn(commentCompiledStatement) val order = inOrder(delegateMock, idCompiledStatement, commentCompiledStatement) @@ -324,7 +324,7 @@ class MappingSupportSQLiteDatabaseTest { @Test fun testCompileStatement() { - whenCalled(delegateMock.isOpen).thenReturn(true) + whenever(delegateMock.isOpen).thenReturn(true) val idSql = "INSERT INTO `id_test` (`col1`) VALUES ('val1')" val commentSql = "INSERT INTO `comment_test` (`col2`) VALUES ('val2')" @@ -445,7 +445,7 @@ class MappingSupportSQLiteDatabaseTest { verifyNoMoreInteractions(delegateMock) val results = expected.asSequence().zip(compiledStatements.asSequence()).groupBy({ it.first }) { it.second } - whenCalled(delegateMock.compileStatement(reifiedAnyOrNull())).then(throwingInvocationHandler(false, results)) + whenever(delegateMock.compileStatement(reifiedAnyOrNull())).then(throwingInvocationHandler(false, results)) repeat(statementCount) { index -> val data = newBoundStatementData[index] @@ -648,21 +648,21 @@ private fun throwingInvocationHandler(retainLast: Boolean, handledResults private fun mockCancellationSignal(isCanceled: Boolean): CancellationSignal { val mock = mock(CancellationSignal::class.java) - whenCalled(mock.isCanceled).thenReturn(isCanceled) - whenCalled(mock.toString()).thenReturn("Mock(isCanceled=$isCanceled)") + whenever(mock.isCanceled).thenReturn(isCanceled) + whenever(mock.toString()).thenReturn("Mock(isCanceled=$isCanceled)") return mock } private fun mockSupportSQLiteStatement(): Pair> { val bindings: MutableMap = mutableMapOf() val mock = mock(SupportSQLiteStatement::class.java) - whenCalled(mock.bindString(anyInt(), anyString())).thenDo { + whenever(mock.bindString(anyInt(), anyString())).thenDo { bindings[it.getArgument(0, Integer::class.java).toInt()] = it.getArgument(1, String::class.java) } - whenCalled(mock.bindLong(anyInt(), anyLong())).thenDo { + whenever(mock.bindLong(anyInt(), anyLong())).thenDo { bindings[it.getArgument(0, Integer::class.java).toInt()] = it.getArgument(1, java.lang.Long::class.java) } - whenCalled(mock.bindNull(anyInt())).thenDo { + whenever(mock.bindNull(anyInt())).thenDo { bindings[it.getArgument(0, Integer::class.java).toInt()] = null } return mock to bindings @@ -674,14 +674,14 @@ private fun mockContentValues(vararg elements: Pair): ContentValue private fun mockContentValues(entries: Map): ContentValues { val mock = mock(ContentValues::class.java) - whenCalled(mock.valueSet()).thenReturn(entries.entries) - whenCalled(mock.size()).thenReturn(entries.size) - whenCalled(mock.isEmpty).thenReturn(entries.isEmpty()) - whenCalled(mock.keySet()).thenReturn(entries.keys) - whenCalled(mock.get(anyString())).then { + whenever(mock.valueSet()).thenReturn(entries.entries) + whenever(mock.size()).thenReturn(entries.size) + whenever(mock.isEmpty).thenReturn(entries.isEmpty()) + whenever(mock.keySet()).thenReturn(entries.keys) + whenever(mock.get(anyString())).then { entries[it.getArgument(0, String::class.java)] } - whenCalled(mock.toString()).thenReturn("Mock${entries}") + whenever(mock.toString()).thenReturn("Mock${entries}") return mock } From 2c172836127864e7e0d651156d74fe572942e31e Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:45:18 +0200 Subject: [PATCH 107/120] Moved "pseudo equality" methods to dedicated class *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 87 +------------------ .../data/testing/PseudoEquality.kt | 85 ++++++++++++++++++ 2 files changed, 89 insertions(+), 83 deletions(-) create mode 100644 data/src/test/java/org/cryptomator/data/testing/PseudoEquality.kt diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 1551bfbf1..d03bd4cb2 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -1,9 +1,7 @@ package org.cryptomator.data.db.sqlmapping -import org.mockito.ArgumentMatchers.argThat as defaultArgThat import org.mockito.kotlin.any as reifiedAny import org.mockito.kotlin.anyOrNull as reifiedAnyOrNull -import org.mockito.kotlin.argThat as reifiedArgThat import android.content.ContentValues import android.database.Cursor import android.database.MatrixCursor @@ -19,6 +17,10 @@ import org.cryptomator.data.db.sqlmapping.Mapping.COMMENT import org.cryptomator.data.db.sqlmapping.Mapping.COUNTER import org.cryptomator.data.db.sqlmapping.Mapping.IDENTITY import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement +import org.cryptomator.data.testing.ValueExtractor +import org.cryptomator.data.testing.anyPseudoEquals +import org.cryptomator.data.testing.anyPseudoEqualsUnlessNull +import org.cryptomator.data.testing.asCached import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNotSame @@ -32,7 +34,6 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.mockito.ArgumentMatcher import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mockito.anyString @@ -46,7 +47,6 @@ import org.mockito.kotlin.KInOrder import org.mockito.kotlin.anyArray import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder -import org.mockito.kotlin.isNull import org.mockito.kotlin.whenever import org.mockito.stubbing.Answer import org.mockito.stubbing.OngoingStubbing @@ -505,87 +505,8 @@ enum class Mapping { IDENTITY, COMMENT, COUNTER } private const val SENTINEL = "::SENTINEL::" -private inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set>): T? { - return if (other != null) defaultArgThat(NullHandlingMatcher(pseudoEquals(other, valueExtractors), false)) else isNull() -} - -private inline fun anyPseudoEquals(other: T, valueExtractors: Set>): T { - return reifiedArgThat(pseudoEquals(other, valueExtractors)) -} - -private fun pseudoEquals(other: T, valueExtractors: Set>): ArgumentMatcher { - require(valueExtractors.isNotEmpty()) - return PseudoEqualsMatcher(other, valueExtractors) -} - -private class PseudoEqualsMatcher( // - private val other: T, // - private val valueExtractors: Set> // -) : ArgumentMatcher { - - override fun matches(argument: T): Boolean { - if (argument === other) { - return true - } - return valueExtractors.all { extractor -> extractor(argument) == extractor(other) } - } -} - -private typealias ValueExtractor = (T) -> Any? - -private data class CacheKey(val wrappedKey: T) { - - override fun hashCode(): Int { - return if (isPrimitive(wrappedKey)) { - wrappedKey!!.hashCode() - } else { - System.identityHashCode(wrappedKey) - } - } - - override fun equals(other: Any?): Boolean { - if (other == null || other !is CacheKey<*>) { - return false - } - return if (isPrimitive(this.wrappedKey) && isPrimitive(other.wrappedKey)) { - this.wrappedKey == other.wrappedKey - } else { - this.wrappedKey === other.wrappedKey - } - } -} - -private data class CacheValue(val wrappedValue: Any?) //Allows correct handling of nulls - -private fun isPrimitive(obj: Any?): Boolean { - return when (obj) { - is Boolean, Char, Byte, Short, Int, Long, Float, Double -> true - else -> false - } -} - -private fun ValueExtractor.asCached(): ValueExtractor { - val cache = mutableMapOf, CacheValue>() - return { - cache.computeIfAbsent(CacheKey(it)) { key -> CacheValue(this@asCached(key.wrappedKey)) }.wrappedValue - } -} - private inline fun OngoingStubbing.thenDo(crossinline action: (invocation: InvocationOnMock) -> Unit): OngoingStubbing = thenAnswer { action(it) } -private class NullHandlingMatcher( // - private val delegate: ArgumentMatcher, // - private val matchNull: Boolean // -) : ArgumentMatcher { - - override fun matches(argument: T?): Boolean { - if (argument == null) { - return matchNull - } - return delegate.matches(argument) - } -} - private fun newCachedSupportSQLiteQueryProperties(): Set> = setOf( // SupportSQLiteQuery::sql.asCached(), // SupportSQLiteQuery::argCount, // diff --git a/data/src/test/java/org/cryptomator/data/testing/PseudoEquality.kt b/data/src/test/java/org/cryptomator/data/testing/PseudoEquality.kt new file mode 100644 index 000000000..ed6addf47 --- /dev/null +++ b/data/src/test/java/org/cryptomator/data/testing/PseudoEquality.kt @@ -0,0 +1,85 @@ +package org.cryptomator.data.testing + +import org.mockito.ArgumentMatchers.argThat as defaultArgThat +import org.mockito.kotlin.argThat as reifiedArgThat +import org.mockito.ArgumentMatcher +import org.mockito.kotlin.isNull + +internal inline fun anyPseudoEqualsUnlessNull(other: T?, valueExtractors: Set>): T? { + return if (other != null) defaultArgThat(NullHandlingMatcher(pseudoEquals(other, valueExtractors), false)) else isNull() +} + +internal inline fun anyPseudoEquals(other: T, valueExtractors: Set>): T { + return reifiedArgThat(pseudoEquals(other, valueExtractors)) +} + +internal fun pseudoEquals(other: T, valueExtractors: Set>): ArgumentMatcher { + require(valueExtractors.isNotEmpty()) + return PseudoEqualsMatcher(other, valueExtractors) +} + +internal class PseudoEqualsMatcher( // + private val other: T, // + private val valueExtractors: Set> // +) : ArgumentMatcher { + + override fun matches(argument: T): Boolean { + if (argument === other) { + return true + } + return valueExtractors.all { extractor -> extractor(argument) == extractor(other) } + } +} + +internal typealias ValueExtractor = (T) -> Any? + +private data class CacheKey(val wrappedKey: T) { + + override fun hashCode(): Int { + return if (isPrimitive(wrappedKey)) { + wrappedKey!!.hashCode() + } else { + System.identityHashCode(wrappedKey) + } + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is CacheKey<*>) { + return false + } + return if (isPrimitive(this.wrappedKey) && isPrimitive(other.wrappedKey)) { + this.wrappedKey == other.wrappedKey + } else { + this.wrappedKey === other.wrappedKey + } + } +} + +private data class CacheValue(val wrappedValue: Any?) //Allows correct handling of nulls + +private fun isPrimitive(obj: Any?): Boolean { + return when (obj) { + is Boolean, Char, Byte, Short, Int, Long, Float, Double -> true + else -> false + } +} + +internal fun ValueExtractor.asCached(): ValueExtractor { + val cache = mutableMapOf, CacheValue>() + return { + cache.computeIfAbsent(CacheKey(it)) { key -> CacheValue(this@asCached(key.wrappedKey)) }.wrappedValue + } +} + +internal class NullHandlingMatcher( // + private val delegate: ArgumentMatcher, // + private val matchNull: Boolean // +) : ArgumentMatcher { + + override fun matches(argument: T?): Boolean { + if (argument == null) { + return matchNull + } + return delegate.matches(argument) + } +} \ No newline at end of file From c581e779f545520b3b942cc0dde7bae1813e317b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:03:41 +0200 Subject: [PATCH 108/120] Moved helper methods for "Iterables" to dedicated class *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabaseTest.kt | 34 ++++--------------- .../org/cryptomator/data/testing/Iterables.kt | 32 +++++++++++++++++ 2 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 data/src/test/java/org/cryptomator/data/testing/Iterables.kt diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index d03bd4cb2..448994d58 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -20,7 +20,14 @@ import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSu import org.cryptomator.data.testing.ValueExtractor import org.cryptomator.data.testing.anyPseudoEquals import org.cryptomator.data.testing.anyPseudoEqualsUnlessNull +import org.cryptomator.data.testing.argCount import org.cryptomator.data.testing.asCached +import org.cryptomator.data.testing.cartesianProductFour +import org.cryptomator.data.testing.cartesianProductThree +import org.cryptomator.data.testing.cartesianProductTwo +import org.cryptomator.data.testing.nullCount +import org.cryptomator.data.testing.toArgumentsStream +import org.cryptomator.data.testing.toBindingsMap import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNotSame @@ -1103,31 +1110,4 @@ fun sourceForTestNewBoundStatementNumerous2(): Stream { return result // .map { it.toList() } // .toArgumentsStream() -} - -fun Sequence.cartesianProductTwo(other: Iterable): Sequence> = flatMap { a -> - other.asSequence().map { b -> a to b } -} - -fun Sequence>.cartesianProductThree(other: Iterable): Sequence> = flatMap { abPair -> - other.asSequence().map { c -> Triple(abPair.first, abPair.second, c) } -} - -fun Sequence>.cartesianProductFour(other: Iterable): Sequence> = flatMap { triple -> - other.asSequence().map { otherElement -> listOf(triple.first, triple.second, triple.third, otherElement) } -} - -fun Sequence>.toArgumentsStream(): Stream = map { - Arguments { it.toTypedArray() } -}.asStream() - -private fun Iterable?.nullCount(): Int = this?.count { it == null } ?: 0 - -private inline fun Iterable?.argCount(): Int = this?.asSequence()?.filterIsInstance()?.count() ?: 0 - -private fun Iterable?.toBindingsMap(): Map { - return this?.asSequence() // - ?.map { if (it is Int) it.toLong() else it } // Required because java.lang.Integer.valueOf(x) != java.lang.Long.valueOf(x) - ?.mapIndexed { index, value -> index + 1 to value } // - ?.toMap() ?: emptyMap() } \ No newline at end of file diff --git a/data/src/test/java/org/cryptomator/data/testing/Iterables.kt b/data/src/test/java/org/cryptomator/data/testing/Iterables.kt new file mode 100644 index 000000000..8949a7dbb --- /dev/null +++ b/data/src/test/java/org/cryptomator/data/testing/Iterables.kt @@ -0,0 +1,32 @@ +package org.cryptomator.data.testing + +import org.junit.jupiter.params.provider.Arguments +import java.util.stream.Stream +import kotlin.streams.asStream + +fun Sequence.cartesianProductTwo(other: Iterable): Sequence> = flatMap { a -> + other.asSequence().map { b -> a to b } +} + +fun Sequence>.cartesianProductThree(other: Iterable): Sequence> = flatMap { abPair -> + other.asSequence().map { c -> Triple(abPair.first, abPair.second, c) } +} + +fun Sequence>.cartesianProductFour(other: Iterable): Sequence> = flatMap { triple -> + other.asSequence().map { otherElement -> listOf(triple.first, triple.second, triple.third, otherElement) } +} + +fun Sequence>.toArgumentsStream(): Stream = map { + Arguments { it.toTypedArray() } +}.asStream() + +fun Iterable?.nullCount(): Int = this?.count { it == null } ?: 0 + +inline fun Iterable?.argCount(): Int = this?.asSequence()?.filterIsInstance()?.count() ?: 0 + +fun Iterable?.toBindingsMap(): Map { + return this?.asSequence() // + ?.map { if (it is Int) it.toLong() else it } // Required because java.lang.Integer.valueOf(x) != java.lang.Long.valueOf(x) + ?.mapIndexed { index, value -> index + 1 to value } // + ?.toMap() ?: emptyMap() +} \ No newline at end of file From 3f35615e88a8f77c6bcdae9b782c22e709c3bc9c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:43:49 +0200 Subject: [PATCH 109/120] Added type-checks to tests for "MappingSupportSQLiteQuery" Added "@VisibleForTesting" to "MappingSupportSQLiteQuery" and changed its visibility to internal *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../MappingSupportSQLiteDatabase.kt | 3 +- .../MappingSupportSQLiteDatabaseTest.kt | 32 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt index 45640f4a7..774774e59 100644 --- a/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabase.kt @@ -179,7 +179,8 @@ internal class MappingSupportSQLiteDatabase( } } - private inner class MappingSupportSQLiteQuery( + @VisibleForTesting + internal inner class MappingSupportSQLiteQuery( private val delegateQuery: SupportSQLiteQuery ) : SupportSQLiteQuery by delegateQuery { diff --git a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt index 448994d58..8721e1408 100644 --- a/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt +++ b/data/src/test/java/org/cryptomator/data/db/sqlmapping/MappingSupportSQLiteDatabaseTest.kt @@ -16,6 +16,7 @@ import androidx.sqlite.db.SupportSQLiteStatement import org.cryptomator.data.db.sqlmapping.Mapping.COMMENT import org.cryptomator.data.db.sqlmapping.Mapping.COUNTER import org.cryptomator.data.db.sqlmapping.Mapping.IDENTITY +import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteQuery import org.cryptomator.data.db.sqlmapping.MappingSupportSQLiteDatabase.MappingSupportSQLiteStatement import org.cryptomator.data.testing.ValueExtractor import org.cryptomator.data.testing.anyPseudoEquals @@ -51,6 +52,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.internal.verification.VerificationModeFactory.times import org.mockito.invocation.InvocationOnMock import org.mockito.kotlin.KInOrder +import org.mockito.kotlin.and import org.mockito.kotlin.anyArray import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder @@ -140,10 +142,16 @@ class MappingSupportSQLiteDatabaseTest { val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( // - anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), supportSQLiteQueryProperties) + and( // + reifiedAny(), // + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test`"), supportSQLiteQueryProperties) // + ) ) verify(delegateMock).query( // - anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!"), supportSQLiteQueryProperties) + and( // + reifiedAny(), // + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` -- Comment!"), supportSQLiteQueryProperties) // + ) ) verifyNoMoreInteractions(delegateMock) } @@ -157,10 +165,16 @@ class MappingSupportSQLiteDatabaseTest { val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( // - anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), supportSQLiteQueryProperties) + and( // + reifiedAny(), // + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `id_test` WHERE `col` = ?", arrayOf("test1")), supportSQLiteQueryProperties) // + ) ) verify(delegateMock).query( // - anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")), supportSQLiteQueryProperties) + and( // + reifiedAny(), // + anyPseudoEquals(SimpleSQLiteQuery("SELECT `col` FROM `comment_test` WHERE `col` = ? -- Comment!", arrayOf("test2")), supportSQLiteQueryProperties) // + ) ) verifyNoMoreInteractions(delegateMock) } @@ -175,11 +189,17 @@ class MappingSupportSQLiteDatabaseTest { val supportSQLiteQueryProperties = newCachedSupportSQLiteQueryProperties() verify(delegateMock).query( // - anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), // + and( + reifiedAny(), // + anyPseudoEquals(queries.idExpected, supportSQLiteQueryProperties), // + ), // anyPseudoEqualsUnlessNull(signals.idExpected, setOf>(CancellationSignal::isCanceled)) ) verify(delegateMock).query( // - anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), // + and( + reifiedAny(), // + anyPseudoEquals(queries.commentExpected, supportSQLiteQueryProperties), // + ), // anyPseudoEqualsUnlessNull(signals.commentExpected, setOf>(CancellationSignal::isCanceled)) ) verifyNoMoreInteractions(delegateMock) From 4aa1c902f622ef2051fe6c621956f5e83d3e60fb Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:20:14 +0200 Subject: [PATCH 110/120] Prepared merging upstream v13 --- .../13.json | 232 ------------------ .../14.json | 96 +++++--- .../data/db/CorruptedDatabaseTest.kt | 6 +- .../data/db/UpgradeDatabaseTest.kt | 74 +++--- .../data/db/CryptomatorDatabase.kt | 6 +- .../org/cryptomator/data/db/DatabaseModule.kt | 6 +- .../cryptomator/data/db/migrations/Sql.java | 6 +- ...ration13To14.kt => AutoMigration14To15.kt} | 2 +- .../data/db/migrations/legacy/Upgrade0To1.kt | 6 +- .../db/migrations/legacy/Upgrade10To11.kt | 6 +- .../data/db/migrations/legacy/Upgrade1To2.kt | 2 +- .../data/db/migrations/legacy/Upgrade3To4.kt | 6 +- .../data/db/migrations/legacy/Upgrade4To5.kt | 8 +- .../data/db/migrations/legacy/Upgrade5To6.kt | 8 +- .../data/db/migrations/legacy/Upgrade6To7.kt | 2 +- ...{Migration12To13.kt => Migration13To14.kt} | 14 +- 16 files changed, 133 insertions(+), 347 deletions(-) delete mode 100644 data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json rename data/src/main/java/org/cryptomator/data/db/migrations/auto/{AutoMigration13To14.kt => AutoMigration14To15.kt} (96%) rename data/src/main/java/org/cryptomator/data/db/migrations/manual/{Migration12To13.kt => Migration13To14.kt} (72%) diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json deleted file mode 100644 index ff8bfad70..000000000 --- a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/13.json +++ /dev/null @@ -1,232 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 13, - "identityHash": "932cb16d86be1f723e0e4b0ef2bc542a", - "entities": [ - { - "tableName": "CLOUD_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `TYPE` TEXT NOT NULL, `ACCESS_TOKEN` TEXT, `URL` TEXT, `USERNAME` TEXT, `WEBDAV_CERTIFICATE` TEXT, `S3_BUCKET` TEXT, `S3_REGION` TEXT, `S3_SECRET_KEY` TEXT, PRIMARY KEY(`_id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "_id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "TYPE", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "accessToken", - "columnName": "ACCESS_TOKEN", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "url", - "columnName": "URL", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "username", - "columnName": "USERNAME", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "webdavCertificate", - "columnName": "WEBDAV_CERTIFICATE", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "s3Bucket", - "columnName": "S3_BUCKET", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "s3Region", - "columnName": "S3_REGION", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "s3SecretKey", - "columnName": "S3_SECRET_KEY", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "_id" - ] - }, - "indices": [], - "foreignKeys": [] - }, - { - "tableName": "UPDATE_CHECK_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LICENSE_TOKEN` TEXT, `RELEASE_NOTE` TEXT, `VERSION` TEXT, `URL_TO_APK` TEXT, `APK_SHA256` TEXT, `URL_TO_RELEASE_NOTE` TEXT, PRIMARY KEY(`_id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "_id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "licenseToken", - "columnName": "LICENSE_TOKEN", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "releaseNote", - "columnName": "RELEASE_NOTE", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "version", - "columnName": "VERSION", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "urlToApk", - "columnName": "URL_TO_APK", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "apkSha256", - "columnName": "APK_SHA256", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "urlToReleaseNote", - "columnName": "URL_TO_RELEASE_NOTE", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "_id" - ] - }, - "indices": [], - "foreignKeys": [] - }, - { - "tableName": "VAULT_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `FOLDER_CLOUD_ID` INTEGER, `FOLDER_PATH` TEXT, `FOLDER_NAME` TEXT, `CLOUD_TYPE` TEXT NOT NULL, `PASSWORD` TEXT, `POSITION` INTEGER, `FORMAT` INTEGER, `SHORTENING_THRESHOLD` INTEGER, PRIMARY KEY(`_id`), FOREIGN KEY(`FOLDER_CLOUD_ID`) REFERENCES `CLOUD_ENTITY`(`_id`) ON UPDATE NO ACTION ON DELETE SET NULL )", - "fields": [ - { - "fieldPath": "id", - "columnName": "_id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "folderCloudId", - "columnName": "FOLDER_CLOUD_ID", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "folderPath", - "columnName": "FOLDER_PATH", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "folderName", - "columnName": "FOLDER_NAME", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "cloudType", - "columnName": "CLOUD_TYPE", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "password", - "columnName": "PASSWORD", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "position", - "columnName": "POSITION", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "format", - "columnName": "FORMAT", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "shorteningThreshold", - "columnName": "SHORTENING_THRESHOLD", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "_id" - ] - }, - "indices": [ - { - "name": "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", - "unique": true, - "columnNames": [ - "FOLDER_PATH", - "FOLDER_CLOUD_ID" - ], - "orders": [ - "ASC", - "ASC" - ], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`FOLDER_PATH` ASC, `FOLDER_CLOUD_ID` ASC)" - } - ], - "foreignKeys": [ - { - "table": "CLOUD_ENTITY", - "onDelete": "SET NULL", - "onUpdate": "NO ACTION", - "columns": [ - "FOLDER_CLOUD_ID" - ], - "referencedColumns": [ - "_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, '932cb16d86be1f723e0e4b0ef2bc542a')" - ] - } -} \ No newline at end of file diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json index 78e185abe..60e92a308 100644 --- a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/14.json @@ -2,63 +2,75 @@ "formatVersion": 1, "database": { "version": 14, - "identityHash": "09c519f6022b510f2ef05f52570806d9", + "identityHash": "b8c52ca7bdf9dce0036787a18080b679", "entities": [ { "tableName": "CLOUD_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `type` TEXT NOT NULL, `accessToken` TEXT, `url` TEXT, `username` TEXT, `webdavCertificate` TEXT, `s3Bucket` TEXT, `s3Region` TEXT, `s3SecretKey` TEXT, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `TYPE` TEXT NOT NULL, `ACCESS_TOKEN` TEXT, `ACCESS_TOKEN_CRYPTO_MODE` TEXT, `URL` TEXT, `USERNAME` TEXT, `WEBDAV_CERTIFICATE` TEXT, `S3_BUCKET` TEXT, `S3_REGION` TEXT, `S3_SECRET_KEY` TEXT, `S3_SECRET_KEY_CRYPTO_MODE` TEXT, PRIMARY KEY(`_id`))", "fields": [ { "fieldPath": "id", - "columnName": "id", + "columnName": "_id", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "type", - "columnName": "type", + "columnName": "TYPE", "affinity": "TEXT", "notNull": true }, { "fieldPath": "accessToken", - "columnName": "accessToken", + "columnName": "ACCESS_TOKEN", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accessTokenCryptoMode", + "columnName": "ACCESS_TOKEN_CRYPTO_MODE", "affinity": "TEXT", "notNull": false }, { "fieldPath": "url", - "columnName": "url", + "columnName": "URL", "affinity": "TEXT", "notNull": false }, { "fieldPath": "username", - "columnName": "username", + "columnName": "USERNAME", "affinity": "TEXT", "notNull": false }, { "fieldPath": "webdavCertificate", - "columnName": "webdavCertificate", + "columnName": "WEBDAV_CERTIFICATE", "affinity": "TEXT", "notNull": false }, { "fieldPath": "s3Bucket", - "columnName": "s3Bucket", + "columnName": "S3_BUCKET", "affinity": "TEXT", "notNull": false }, { "fieldPath": "s3Region", - "columnName": "s3Region", + "columnName": "S3_REGION", "affinity": "TEXT", "notNull": false }, { "fieldPath": "s3SecretKey", - "columnName": "s3SecretKey", + "columnName": "S3_SECRET_KEY", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3SecretKeyCryptoMode", + "columnName": "S3_SECRET_KEY_CRYPTO_MODE", "affinity": "TEXT", "notNull": false } @@ -66,7 +78,7 @@ "primaryKey": { "autoGenerate": false, "columnNames": [ - "id" + "_id" ] }, "indices": [], @@ -74,47 +86,47 @@ }, { "tableName": "UPDATE_CHECK_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `licenseToken` TEXT, `releaseNote` TEXT, `version` TEXT, `urlToApk` TEXT, `apkSha256` TEXT, `urlToReleaseNote` TEXT, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LICENSE_TOKEN` TEXT, `RELEASE_NOTE` TEXT, `VERSION` TEXT, `URL_TO_APK` TEXT, `APK_SHA256` TEXT, `URL_TO_RELEASE_NOTE` TEXT, PRIMARY KEY(`_id`))", "fields": [ { "fieldPath": "id", - "columnName": "id", + "columnName": "_id", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "licenseToken", - "columnName": "licenseToken", + "columnName": "LICENSE_TOKEN", "affinity": "TEXT", "notNull": false }, { "fieldPath": "releaseNote", - "columnName": "releaseNote", + "columnName": "RELEASE_NOTE", "affinity": "TEXT", "notNull": false }, { "fieldPath": "version", - "columnName": "version", + "columnName": "VERSION", "affinity": "TEXT", "notNull": false }, { "fieldPath": "urlToApk", - "columnName": "urlToApk", + "columnName": "URL_TO_APK", "affinity": "TEXT", "notNull": false }, { "fieldPath": "apkSha256", - "columnName": "apkSha256", + "columnName": "APK_SHA256", "affinity": "TEXT", "notNull": false }, { "fieldPath": "urlToReleaseNote", - "columnName": "urlToReleaseNote", + "columnName": "URL_TO_RELEASE_NOTE", "affinity": "TEXT", "notNull": false } @@ -122,7 +134,7 @@ "primaryKey": { "autoGenerate": false, "columnNames": [ - "id" + "_id" ] }, "indices": [], @@ -130,59 +142,65 @@ }, { "tableName": "VAULT_ENTITY", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `folderCloudId` INTEGER, `folderPath` TEXT, `folderName` TEXT, `cloudType` TEXT NOT NULL, `password` TEXT, `position` INTEGER, `format` INTEGER, `shorteningThreshold` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`folderCloudId`) REFERENCES `CLOUD_ENTITY`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `FOLDER_CLOUD_ID` INTEGER, `FOLDER_PATH` TEXT, `FOLDER_NAME` TEXT, `CLOUD_TYPE` TEXT NOT NULL, `PASSWORD` TEXT, `PASSWORD_CRYPTO_MODE` TEXT, `POSITION` INTEGER, `FORMAT` INTEGER, `SHORTENING_THRESHOLD` INTEGER, PRIMARY KEY(`_id`), FOREIGN KEY(`FOLDER_CLOUD_ID`) REFERENCES `CLOUD_ENTITY`(`_id`) ON UPDATE NO ACTION ON DELETE SET NULL )", "fields": [ { "fieldPath": "id", - "columnName": "id", + "columnName": "_id", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "folderCloudId", - "columnName": "folderCloudId", + "columnName": "FOLDER_CLOUD_ID", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "folderPath", - "columnName": "folderPath", + "columnName": "FOLDER_PATH", "affinity": "TEXT", "notNull": false }, { "fieldPath": "folderName", - "columnName": "folderName", + "columnName": "FOLDER_NAME", "affinity": "TEXT", "notNull": false }, { "fieldPath": "cloudType", - "columnName": "cloudType", + "columnName": "CLOUD_TYPE", "affinity": "TEXT", "notNull": true }, { "fieldPath": "password", - "columnName": "password", + "columnName": "PASSWORD", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "passwordCryptoMode", + "columnName": "PASSWORD_CRYPTO_MODE", "affinity": "TEXT", "notNull": false }, { "fieldPath": "position", - "columnName": "position", + "columnName": "POSITION", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "format", - "columnName": "format", + "columnName": "FORMAT", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "shorteningThreshold", - "columnName": "shorteningThreshold", + "columnName": "SHORTENING_THRESHOLD", "affinity": "INTEGER", "notNull": false } @@ -190,7 +208,7 @@ "primaryKey": { "autoGenerate": false, "columnNames": [ - "id" + "_id" ] }, "indices": [ @@ -198,26 +216,26 @@ "name": "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", "unique": true, "columnNames": [ - "folderPath", - "folderCloudId" + "FOLDER_PATH", + "FOLDER_CLOUD_ID" ], "orders": [ "ASC", "ASC" ], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`folderPath` ASC, `folderCloudId` ASC)" + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`FOLDER_PATH` ASC, `FOLDER_CLOUD_ID` ASC)" } ], "foreignKeys": [ { "table": "CLOUD_ENTITY", - "onDelete": "RESTRICT", + "onDelete": "SET NULL", "onUpdate": "NO ACTION", "columns": [ - "folderCloudId" + "FOLDER_CLOUD_ID" ], "referencedColumns": [ - "id" + "_id" ] } ] @@ -226,7 +244,7 @@ "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, '09c519f6022b510f2ef05f52570806d9')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b8c52ca7bdf9dce0036787a18080b679')" ] } } \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 69cff3f34..cb3cc893a 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -20,7 +20,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 -import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.migrations.manual.Migration13To14 import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.data.util.useFinally @@ -79,8 +79,8 @@ class CorruptedDatabaseTest { Upgrade10To11(), Upgrade11To12(sharedPreferencesHandler), // - Migration12To13(), - //Auto: 13 -> 14 + Migration13To14(), + //Auto: 14 -> 15 ) createVersion0Database(context, TEST_DB) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 23c896f1f..34085ecf4 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -27,7 +27,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 -import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.migrations.manual.Migration13To14 import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.domain.CloudType @@ -48,7 +48,7 @@ import java.io.IOException import java.nio.file.Files private const val TEST_DB = "migration-test" -private const val LATEST_LEGACY_MIGRATION = 12 +private const val LATEST_LEGACY_MIGRATION = 13 private const val UUID_LENGTH = 36 @@ -135,8 +135,8 @@ class UpgradeDatabaseTest { Upgrade11To12(sharedPreferencesHandler).migrate(db) db.close() - runMigrationsAndValidate(13, Migration12To13()) - runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()) + runMigrationsAndValidate(14, Migration13To14()) + runMigrationsAndValidate(15, CryptomatorDatabase_AutoMigration_14_15_Impl()) } @Throws(IOException::class) @@ -402,7 +402,7 @@ class UpgradeDatabaseTest { Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) Sql.createTable("UPDATE_CHECK_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // @@ -715,7 +715,7 @@ class UpgradeDatabaseTest { } @Test - fun migrate12To14ForeignKeySideEffects() { //See: Migration12To13 + fun migrate13To15ForeignKeySideEffects() { //See: Migration13To14 Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -728,18 +728,18 @@ class UpgradeDatabaseTest { Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) - val pre13Statement = referencesStatement(db) - val pre13Expected = "CONSTRAINT FK_FOLDER_CLOUD_ID_CLOUD_ENTITY FOREIGN KEY (FOLDER_CLOUD_ID) REFERENCES CLOUD_ENTITY(_id) ON DELETE SET NULL" + val pre14Statement = referencesStatement(db) + val pre14Expected = "CONSTRAINT FK_FOLDER_CLOUD_ID_CLOUD_ENTITY FOREIGN KEY (FOLDER_CLOUD_ID) REFERENCES CLOUD_ENTITY(_id) ON DELETE SET NULL" //This is a sanity check and may need to be updated if Sql.java is changed - assertTrue("Expected \".*$pre13Expected.*\", got \"$pre13Statement\"", pre13Statement.contains(pre13Expected)) + assertTrue("Expected \".*$pre14Expected.*\", got \"$pre14Statement\"", pre14Statement.contains(pre14Expected)) db.close() - runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> val statement = referencesStatement(migratedDb) - assertEquals(pre13Statement, statement) + assertEquals(pre14Statement, statement) } - runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + runMigrationsAndValidate(15, CryptomatorDatabase_AutoMigration_14_15_Impl()).also { migratedDb -> val statement = referencesStatement(migratedDb) val expected = "FOREIGN KEY(folderCloudId) REFERENCES CLOUD_ENTITY(id) ON" assertTrue("Expected \".*$expected.*\", got \"$statement\"", statement.contains(expected)) @@ -764,7 +764,7 @@ class UpgradeDatabaseTest { } @Test - fun migrate12To14IndexSideEffects() { //See: Migration12To13 + fun migrate13To15IndexSideEffects() { //See: Migration13To14 Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -777,19 +777,19 @@ class UpgradeDatabaseTest { Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) - val pre13Statement = indexStatement(db) - val pre13Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC) -- " + val pre14Statement = indexStatement(db) + val pre14Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC) -- " //This is a sanity check and may need to be updated if Sql.java is changed - assertEquals(pre13Expected, pre13Statement.substring(0, pre13Statement.length - UUID_LENGTH)) - assertIsUUID(pre13Statement.substring(pre13Statement.length - UUID_LENGTH)) + assertEquals(pre14Expected, pre14Statement.substring(0, pre14Statement.length - UUID_LENGTH)) + assertIsUUID(pre14Statement.substring(pre14Statement.length - UUID_LENGTH)) db.close() - runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> val statement = indexStatement(migratedDb) - assertEquals(pre13Statement, statement) + assertEquals(pre14Statement, statement) } - runMigrationsAndValidate(14, CryptomatorDatabase_AutoMigration_13_14_Impl()).also { migratedDb -> + runMigrationsAndValidate(15, CryptomatorDatabase_AutoMigration_14_15_Impl()).also { migratedDb -> val statement = indexStatement(migratedDb) val expected = "CREATE UNIQUE INDEX `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `VAULT_ENTITY` (`folderPath` ASC, `folderCloudId` ASC)" assertEquals(expected, statement) @@ -810,7 +810,7 @@ class UpgradeDatabaseTest { } @Test - fun migrate12To13() { + fun migrate13To14() { Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -823,18 +823,18 @@ class UpgradeDatabaseTest { Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) - assertEquals(12, db.version) - val pre13Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> + assertEquals(13, db.version) + val pre14Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> val cursor = Sql.query(tableName).executeOn(db) copyAndClose(cursor) } db.close() - runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) - assertEquals(13, migratedDb.version) + assertEquals(14, migratedDb.version) - for (preTable in pre13Tables) { + for (preTable in pre14Tables) { preTable.value.use { preCursor -> Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> assertCursorEquals(preCursor, postCursor) @@ -845,7 +845,7 @@ class UpgradeDatabaseTest { } @Test - fun migrate12To13WithData() { + fun migrate13To14WithData() { Upgrade1To2().migrate(db) Upgrade2To3(context).migrate(db) Upgrade3To4().migrate(db) @@ -903,17 +903,17 @@ class UpgradeDatabaseTest { .set("URL_TO_RELEASE_NOTE", Sql.toString("urlToNote1")) // .executeOn(db) - assertEquals(12, db.version) - val pre13Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> + assertEquals(13, db.version) + val pre14Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> copyAndClose(Sql.query(tableName).executeOn(db)) } db.close() - runMigrationsAndValidate(13, Migration12To13()).also { migratedDb -> + runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) - assertEquals(13, migratedDb.version) + assertEquals(14, migratedDb.version) - for (preTable in pre13Tables) { + for (preTable in pre14Tables) { preTable.value.use { preCursor -> Sql.query(preTable.key).executeOn(migratedDb).use { postCursor -> assertCursorEquals(preCursor, postCursor) @@ -923,15 +923,15 @@ class UpgradeDatabaseTest { } } - //TODO Test metadata of non-entity tables for v13, v14 - //TODO Test metadata and content of entity tables for v14 + //TODO Test metadata of non-entity tables for v14, v15 + //TODO Test metadata and content of entity tables for v15 @Test - fun migrate1To13WithRoom() { + fun migrate1To14WithRoom() { db.version = 1 db.close() runMigrationsAndValidate( - 13, + 14, Upgrade1To2(), Upgrade2To3(context), Upgrade3To4(), @@ -943,7 +943,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler), Upgrade10To11(), Upgrade11To12(sharedPreferencesHandler), - Migration12To13() + Migration13To14() ) } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt index da904d803..ed634b6ae 100644 --- a/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt +++ b/data/src/main/java/org/cryptomator/data/db/CryptomatorDatabase.kt @@ -9,15 +9,15 @@ import org.cryptomator.data.db.entities.CloudEntity import org.cryptomator.data.db.entities.UpdateCheckEntity import org.cryptomator.data.db.entities.VaultEntity import org.cryptomator.data.db.migrations.Sql -import org.cryptomator.data.db.migrations.auto.AutoMigration13To14 +import org.cryptomator.data.db.migrations.auto.AutoMigration14To15 import kotlin.math.max const val DATABASE_NAME = "Cryptomator" -const val CRYPTOMATOR_DATABASE_VERSION = 14 +const val CRYPTOMATOR_DATABASE_VERSION = 15 @Database( version = CRYPTOMATOR_DATABASE_VERSION, entities = [CloudEntity::class, UpdateCheckEntity::class, VaultEntity::class], autoMigrations = [ - AutoMigration(from = 13, to = 14, spec = AutoMigration13To14::class) + AutoMigration(from = 14, to = 15, spec = AutoMigration14To15::class) ] ) abstract class CryptomatorDatabase : RoomDatabase() { diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 3dba945d5..75901e700 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -18,7 +18,7 @@ import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 -import org.cryptomator.data.db.migrations.manual.Migration12To13 +import org.cryptomator.data.db.migrations.manual.Migration13To14 import org.cryptomator.data.db.templating.DbTemplateComponent import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named @@ -149,7 +149,7 @@ class DatabaseModule { upgrade10To11: Upgrade10To11, // upgrade11To12: Upgrade11To12, // // - migration12To13: Migration12To13, // + migration13To14: Migration13To14, // ): Array = arrayOf( upgrade1To2, upgrade2To3, @@ -163,7 +163,7 @@ class DatabaseModule { upgrade10To11, upgrade11To12, // - migration12To13, + migration13To14, ) } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java index 4985ffef0..992009777 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/migrations/Sql.java @@ -350,7 +350,7 @@ public SqlInsertSelectBuilder join(String targetTable, String sourceColumn) { return join(targetTable, "id", sourceColumn); } - public SqlInsertSelectBuilder pre14Join(String targetTable, String sourceColumn) { + public SqlInsertSelectBuilder pre15Join(String targetTable, String sourceColumn) { return join(targetTable, "_id", sourceColumn); } @@ -397,7 +397,7 @@ public SqlCreateTableBuilder id() { return this; } - public SqlCreateTableBuilder pre14Id() { + public SqlCreateTableBuilder pre15Id() { column("_id", INTEGER, PRIMARY_KEY); return this; } @@ -451,7 +451,7 @@ public SqlCreateTableBuilder foreignKey(String column, String targetTable, Forei return foreignKey(column, targetTable, "id", behaviours); } - public SqlCreateTableBuilder pre14ForeignKey(String column, String targetTable, ForeignKeyBehaviour... behaviours) { + public SqlCreateTableBuilder pre15ForeignKey(String column, String targetTable, ForeignKeyBehaviour... behaviours) { return foreignKey(column, targetTable, "_id", behaviours); } diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt similarity index 96% rename from data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt index e1778cec5..0693be41f 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration13To14.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt @@ -32,4 +32,4 @@ import org.cryptomator.data.db.DatabaseAutoMigrationSpec RenameColumn("VAULT_ENTITY", "FORMAT", "format"), RenameColumn("VAULT_ENTITY", "SHORTENING_THRESHOLD", "shorteningThreshold"), ) -class AutoMigration13To14 : DatabaseAutoMigrationSpec() \ No newline at end of file +class AutoMigration14To15 : DatabaseAutoMigrationSpec() \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt index 90555f214..71d0c13d7 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade0To1.kt @@ -22,7 +22,7 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseMigration(0, 1) { private fun createCloudEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("CLOUD_ENTITY") // - .pre14Id() // + .pre15Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("WEBDAV_URL") // @@ -33,13 +33,13 @@ internal class Upgrade0To1 @Inject constructor() : DatabaseMigration(0, 1) { private fun createVaultEntityTable(db: SupportSQLiteDatabase) { Sql.createTable("VAULT_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // - .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.createUniqueIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") // .on("VAULT_ENTITY") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt index 87e8345df..ded90d86d 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade10To11.kt @@ -30,7 +30,7 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseMigration(10, 11) { private fun addFormatAndShorteningToDbEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // @@ -39,14 +39,14 @@ internal class Upgrade10To11 @Inject constructor() : DatabaseMigration(10, 11) { .optionalInt("POSITION") // .optionalInt("FORMAT") // .optionalInt("SHORTENING_THRESHOLD") // - .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre15Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt index 7f0b08035..599aeef9f 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade1To2.kt @@ -18,7 +18,7 @@ internal class Upgrade1To2 @Inject constructor() : DatabaseMigration(1, 2) { db.beginTransaction() try { Sql.createTable("UPDATE_CHECK_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt index 22571eba6..414e0bbf1 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade3To4.kt @@ -24,21 +24,21 @@ internal class Upgrade3To4 @Inject constructor() : DatabaseMigration(3, 4) { private fun addPositionToVaultSchema(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre15Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt index a1b54bb8c..48e830e8e 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade4To5.kt @@ -23,7 +23,7 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // - .pre14Id() // + .pre15Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("URL") // @@ -45,21 +45,21 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseMigration(4, 5) { private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre15Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt index 857f6fc82..b479dd83b 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade5To6.kt @@ -23,7 +23,7 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // - .pre14Id() // + .pre15Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("URL") // @@ -48,21 +48,21 @@ internal class Upgrade5To6 @Inject constructor() : DatabaseMigration(5, 6) { private fun recreateVaultEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // .requiredText("CLOUD_TYPE") // .optionalText("PASSWORD") // .optionalInt("POSITION") // - .pre14ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .pre14Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre15Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt index 6029d1981..3476b6729 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade6To7.kt @@ -25,7 +25,7 @@ internal class Upgrade6To7 @Inject constructor() : DatabaseMigration(6, 7) { Sql.alterTable("UPDATE_CHECK_ENTITY").renameTo("UPDATE_CHECK_ENTITY_OLD").executeOn(db) Sql.createTable("UPDATE_CHECK_ENTITY") // - .pre14Id() // + .pre15Id() // .optionalText("LICENSE_TOKEN") // .optionalText("RELEASE_NOTE") // .optionalText("VERSION") // diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration13To14.kt similarity index 72% rename from data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration13To14.kt index ba0fcc38c..a90ec3014 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/manual/Migration13To14.kt @@ -6,12 +6,12 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Migration12To13 @Inject constructor() : DatabaseMigration(12, 13) { +internal class Migration13To14 @Inject constructor() : DatabaseMigration(13, 14) { - //After migrating from v12 to v13 the database differs as follows: + //After migrating from v13 to v14 the database differs as follows: //1a) The `room_master_table` exists and contains id data - //A v13 database created by room differs from an migrated v13 database as follows: + //A v14 database created by room differs from an migrated v14 database as follows: //1b) The `room_master_table` exists and contains id data //2) The foreign key "VAULT_ENTITY.FOLDER_CLOUD_ID -> CLOUD_ENTITY._id" is unnamed //3) The index "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID" is formatted differently internally @@ -19,11 +19,11 @@ internal class Migration12To13 @Inject constructor() : DatabaseMigration(12, 13) //-- This does never happen in practice as databases are created by upgrading, but illustrates the slightly different schemas. //1a,b and 4 are intended behavior, but 2 and 3 cause the schema and the actual database to slightly drift out of sync. //This *should* not cause any problems. - //The migration to v14 then recreates the tables and therefore resolves 2 and 3 as a side effect. Once the migration to - //v14 has finished, the schema and the database are back in sync. + //The migration to v15 then recreates the tables and therefore resolves 2 and 3 as a side effect. Once the migration to + //v15 has finished, the schema and the database are back in sync. - //Since this is a bit hacky, "UpgradeDatabaseTest" contains the "migrate12To14IndexSideEffects" - //and "migrate12To14ForeignKeySideEffects" methods to bring attention to any potential future changes + //Since this is a bit hacky, "UpgradeDatabaseTest" contains the "migrate13To15IndexSideEffects" + //and "migrate13To15ForeignKeySideEffects" methods to bring attention to any potential future changes //that change this behavior. override fun migrateInternal(db: SupportSQLiteDatabase) { From 2e68cc5850fabd37fed7190e49db14648b75e345 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Thu, 26 Sep 2024 01:15:43 +0200 Subject: [PATCH 111/120] Finished merge of upstream v13 into production logic Added missing fields defined by upstream v13 to "CloudEntity", "VaultEntity" and "AutoMigration14To15" Added room-generated database schema for v15 as "15.json" Moved "Upgrade12To13" and made it runnable Replaced calls in "Upgrade12To13" with matching "pre15"-calls Added "Upgrade12To13" to migration path in "DatabaseModule" -- About this commit, as well as 4aa1c902 and c9a0d3db: A new database version (13) introduced in the upstream by ac5f3ff2/#547 [1] (merged into develop by 7a37c033) needs to be merged into the "feature/dao-migration"-branch used for working on #506 [2]. 4aa1c902 prepared the merge by bumping the versions defined by "feature/dao-migration" from 13 (l12-v1-r2-13) and 14 (l12-v2-r3-14) to 14 (l13-v1-r1-14) and 15 (l13-v2-r1-15). l13-v1-r1-14 supersedes l12-v1-r2-13 and l13-v2-r1-15 supersedes l12-v2-r3-14 by applying the changes introduced by the upstream v13. The relation between l13-v1-r1-14 and l13-v2-r1-15 is the same as between l12-v1-r2-13 and l12-v2-r3-14, i.e. the changes introduced by the upstream v13 are handled according to the semantics defined by that relation. 4aa1c902 deleted "13.json" (was l12-v1-r2-13) and replaced the content of "14.json" (was l12-v2-r3-14) with l13-v1-r1-14, which is the version that supersedes l12-v1-r2-13. It therefore changes the migration path from [...] -> v12 -> v13 (l12-v1-r2-13) -> v14 (l12-v2-r3-14) to [...] -> v12 -> [Not yet merged upstream v13] -> v14 (l13-v1-r1-14) -> v15 (l13-v2-r1-15). c9a0d3db merged the upstream v13 (c970438e) into 4aa1c902 and made the migration path continuous again: [...] -> v12 -> upstream v13 -> v14 (l13-v1-r1-14) -> v15 (l13-v2-r1-15) This commit finishes this merge for the production source. It adds "15.json" with l13-v2-r1-15 as its content, which is the version that supersedes l12-v2-r3-14. Therefore "13.json" (l12-v1-r2-13) and "14.json" (l12-v2-r3-14) are superseded by "14.json" (l13-v1-r1-14) and "15.json" (l13-v2-r1-15) respectively. It also applies l13-v2-r1-15 to "CloudEntity" and "VaultEntity", as well as "AutoMigration14To15". [1] https://github.com/cryptomator/android/pull/547 [2] https://github.com/cryptomator/android/pull/506 Version definitions: l12-v1-r2-13: e1bd29a5 l12-v2-r3-14: 23887b45 l13-v1-r1-14: 9536283c l13-v2-r1-15: 074c77de --- .../15.json | 250 ++++++++++++++++++ .../org/cryptomator/data/db/DatabaseModule.kt | 3 + .../data/db/entities/CloudEntity.kt | 2 + .../data/db/entities/VaultEntity.kt | 1 + .../db/migrations/auto/AutoMigration14To15.kt | 3 + .../{ => migrations/legacy}/Upgrade12To13.kt | 30 ++- 6 files changed, 275 insertions(+), 14 deletions(-) create mode 100644 data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/15.json rename data/src/main/java/org/cryptomator/data/db/{ => migrations/legacy}/Upgrade12To13.kt (82%) diff --git a/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/15.json b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/15.json new file mode 100644 index 000000000..6f4d886a9 --- /dev/null +++ b/data/room/schemas/org.cryptomator.data.db.CryptomatorDatabase/15.json @@ -0,0 +1,250 @@ +{ + "formatVersion": 1, + "database": { + "version": 15, + "identityHash": "cf38b0f4b0e95445e2d2b37ea8df36b6", + "entities": [ + { + "tableName": "CLOUD_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `type` TEXT NOT NULL, `accessToken` TEXT, `accessTokenCryptoMode` TEXT, `url` TEXT, `username` TEXT, `webdavCertificate` TEXT, `s3Bucket` TEXT, `s3Region` TEXT, `s3SecretKey` TEXT, `s3SecretKeyCryptoMode` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accessTokenCryptoMode", + "columnName": "accessTokenCryptoMode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webdavCertificate", + "columnName": "webdavCertificate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Bucket", + "columnName": "s3Bucket", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3Region", + "columnName": "s3Region", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3SecretKey", + "columnName": "s3SecretKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "s3SecretKeyCryptoMode", + "columnName": "s3SecretKeyCryptoMode", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UPDATE_CHECK_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `licenseToken` TEXT, `releaseNote` TEXT, `version` TEXT, `urlToApk` TEXT, `apkSha256` TEXT, `urlToReleaseNote` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "licenseToken", + "columnName": "licenseToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "releaseNote", + "columnName": "releaseNote", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToApk", + "columnName": "urlToApk", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "apkSha256", + "columnName": "apkSha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urlToReleaseNote", + "columnName": "urlToReleaseNote", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "VAULT_ENTITY", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `folderCloudId` INTEGER, `folderPath` TEXT, `folderName` TEXT, `cloudType` TEXT NOT NULL, `password` TEXT, `passwordCryptoMode` TEXT, `position` INTEGER, `format` INTEGER, `shorteningThreshold` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`folderCloudId`) REFERENCES `CLOUD_ENTITY`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderCloudId", + "columnName": "folderCloudId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folderPath", + "columnName": "folderPath", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folderName", + "columnName": "folderName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cloudType", + "columnName": "cloudType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "passwordCryptoMode", + "columnName": "passwordCryptoMode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shorteningThreshold", + "columnName": "shorteningThreshold", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID", + "unique": true, + "columnNames": [ + "folderPath", + "folderCloudId" + ], + "orders": [ + "ASC", + "ASC" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID` ON `${TABLE_NAME}` (`folderPath` ASC, `folderCloudId` ASC)" + } + ], + "foreignKeys": [ + { + "table": "CLOUD_ENTITY", + "onDelete": "RESTRICT", + "onUpdate": "NO ACTION", + "columns": [ + "folderCloudId" + ], + "referencedColumns": [ + "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, 'cf38b0f4b0e95445e2d2b37ea8df36b6')" + ] + } +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 75901e700..33413637c 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -9,6 +9,7 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 @@ -148,6 +149,7 @@ class DatabaseModule { upgrade9To10: Upgrade9To10, // upgrade10To11: Upgrade10To11, // upgrade11To12: Upgrade11To12, // + upgrade12To13: Upgrade12To13, // // migration13To14: Migration13To14, // ): Array = arrayOf( @@ -162,6 +164,7 @@ class DatabaseModule { upgrade9To10, upgrade10To11, upgrade11To12, + upgrade12To13, // migration13To14, ) diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt index 34dc89951..43d859fdc 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.kt @@ -8,12 +8,14 @@ data class CloudEntity( @PrimaryKey override var id: Long?, var type: String, var accessToken: String? = null, + var accessTokenCryptoMode: String? = null, var url: String? = null, var username: String? = null, var webdavCertificate: String? = null, var s3Bucket: String? = null, var s3Region: String? = null, var s3SecretKey: String? = null, + var s3SecretKeyCryptoMode: String? = null, ) : DatabaseEntity { companion object { diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt index 3eb584bc9..755311923 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.kt @@ -18,6 +18,7 @@ data class VaultEntity( val folderName: String?, val cloudType: String, val password: String?, + val passwordCryptoMode: String?, val position: Int?, val format: Int?, val shorteningThreshold: Int?, diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt index 0693be41f..2087f6c5d 100644 --- a/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/auto/AutoMigration14To15.kt @@ -7,12 +7,14 @@ import org.cryptomator.data.db.DatabaseAutoMigrationSpec RenameColumn("CLOUD_ENTITY", "_id", "id"), RenameColumn("CLOUD_ENTITY", "TYPE", "type"), RenameColumn("CLOUD_ENTITY", "ACCESS_TOKEN", "accessToken"), + RenameColumn("CLOUD_ENTITY", "ACCESS_TOKEN_CRYPTO_MODE", "accessTokenCryptoMode"), RenameColumn("CLOUD_ENTITY", "URL", "url"), RenameColumn("CLOUD_ENTITY", "USERNAME", "username"), RenameColumn("CLOUD_ENTITY", "WEBDAV_CERTIFICATE", "webdavCertificate"), RenameColumn("CLOUD_ENTITY", "S3_BUCKET", "s3Bucket"), RenameColumn("CLOUD_ENTITY", "S3_REGION", "s3Region"), RenameColumn("CLOUD_ENTITY", "S3_SECRET_KEY", "s3SecretKey"), + RenameColumn("CLOUD_ENTITY", "S3_SECRET_KEY_CRYPTO_MODE", "s3SecretKeyCryptoMode"), // RenameColumn("UPDATE_CHECK_ENTITY", "_id", "id"), RenameColumn("UPDATE_CHECK_ENTITY", "LICENSE_TOKEN", "licenseToken"), @@ -28,6 +30,7 @@ import org.cryptomator.data.db.DatabaseAutoMigrationSpec RenameColumn("VAULT_ENTITY", "FOLDER_NAME", "folderName"), RenameColumn("VAULT_ENTITY", "CLOUD_TYPE", "cloudType"), RenameColumn("VAULT_ENTITY", "PASSWORD", "password"), + RenameColumn("VAULT_ENTITY", "PASSWORD_CRYPTO_MODE", "passwordCryptoMode"), RenameColumn("VAULT_ENTITY", "POSITION", "position"), RenameColumn("VAULT_ENTITY", "FORMAT", "format"), RenameColumn("VAULT_ENTITY", "SHORTENING_THRESHOLD", "shorteningThreshold"), diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade12To13.kt b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade12To13.kt similarity index 82% rename from data/src/main/java/org/cryptomator/data/db/Upgrade12To13.kt rename to data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade12To13.kt index a6b99a32f..befb30567 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade12To13.kt +++ b/data/src/main/java/org/cryptomator/data/db/migrations/legacy/Upgrade12To13.kt @@ -1,16 +1,18 @@ -package org.cryptomator.data.db +package org.cryptomator.data.db.migrations.legacy import android.content.Context +import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.Sql import org.cryptomator.util.crypto.CredentialCryptor import org.cryptomator.util.crypto.CryptoMode -import org.greenrobot.greendao.database.Database import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class Upgrade12To13 @Inject constructor(private val context: Context) : DatabaseUpgrade(12, 13) { +internal class Upgrade12To13 @Inject constructor(private val context: Context) : DatabaseMigration(12, 13) { - override fun internalApplyTo(db: Database, origin: Int) { + override fun migrateInternal(db: SupportSQLiteDatabase) { db.beginTransaction() try { moveLocalStorageUrlToUrlProperty(db) @@ -24,7 +26,7 @@ internal class Upgrade12To13 @Inject constructor(private val context: Context) : } } - private fun moveLocalStorageUrlToUrlProperty(db: Database) { + private fun moveLocalStorageUrlToUrlProperty(db: SupportSQLiteDatabase) { Sql.query("CLOUD_ENTITY").where("TYPE", Sql.eq("LOCAL")).executeOn(db).use { while (it.moveToNext()) { Sql.update("CLOUD_ENTITY") // @@ -36,18 +38,18 @@ internal class Upgrade12To13 @Inject constructor(private val context: Context) : } } - private fun dropGoogleDriveUsernameInAccessToken(db: Database) { + private fun dropGoogleDriveUsernameInAccessToken(db: SupportSQLiteDatabase) { Sql.update("CLOUD_ENTITY") .set("ACCESS_TOKEN", Sql.toNull()) // .where("TYPE", Sql.eq("GOOGLE_DRIVE")) .executeOn(db) } - private fun addCryptoModeToDbEntities(db: Database) { + private fun addCryptoModeToDbEntities(db: SupportSQLiteDatabase) { Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) Sql.createTable("CLOUD_ENTITY") // - .id() // + .pre15Id() // .requiredText("TYPE") // .optionalText("ACCESS_TOKEN") // .optionalText("ACCESS_TOKEN_CRYPTO_MODE") // @@ -72,10 +74,10 @@ internal class Upgrade12To13 @Inject constructor(private val context: Context) : Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db) } - private fun addPasswordCryptoModeToVaultDbEntity(db: Database) { + private fun addPasswordCryptoModeToVaultDbEntity(db: SupportSQLiteDatabase) { Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) Sql.createTable("VAULT_ENTITY") // - .id() // + .pre15Id() // .optionalInt("FOLDER_CLOUD_ID") // .optionalText("FOLDER_PATH") // .optionalText("FOLDER_NAME") // @@ -85,14 +87,14 @@ internal class Upgrade12To13 @Inject constructor(private val context: Context) : .optionalText("PASSWORD_CRYPTO_MODE") // .optionalInt("POSITION") // .optionalInt("SHORTENING_THRESHOLD") // - .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .pre15ForeignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // .executeOn(db) Sql.insertInto("VAULT_ENTITY") // .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "FORMAT", "PASSWORD", "POSITION", "SHORTENING_THRESHOLD", "CLOUD_ENTITY.TYPE") // .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "FORMAT", "PASSWORD", "POSITION", "SHORTENING_THRESHOLD", "CLOUD_TYPE") // .from("VAULT_ENTITY_OLD") // - .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .pre15Join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // .executeOn(db) Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) @@ -106,14 +108,14 @@ internal class Upgrade12To13 @Inject constructor(private val context: Context) : Sql.dropTable("VAULT_ENTITY_OLD").executeOn(db) } - private fun applyVaultPasswordCryptoModeToDb(db: Database) { + private fun applyVaultPasswordCryptoModeToDb(db: SupportSQLiteDatabase) { Sql.update("VAULT_ENTITY") .set("PASSWORD_CRYPTO_MODE", Sql.toString(CryptoMode.CBC.name)) // .where("PASSWORD", Sql.isNotNull()) .executeOn(db) } - private fun upgradeCloudCryptoModeToGCM(db: Database) { + private fun upgradeCloudCryptoModeToGCM(db: SupportSQLiteDatabase) { val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) From d034f95e74542aaf0f405ea400664a09152cdc3b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:23:15 +0200 Subject: [PATCH 112/120] Added "Upgrade12To13" to migration paths in tests --- .../org/cryptomator/data/db/CorruptedDatabaseTest.kt | 2 ++ .../org/cryptomator/data/db/UpgradeDatabaseTest.kt | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index cb3cc893a..e5bccc473 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -11,6 +11,7 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 @@ -78,6 +79,7 @@ class CorruptedDatabaseTest { Upgrade9To10(sharedPreferencesHandler), Upgrade10To11(), Upgrade11To12(sharedPreferencesHandler), + Upgrade12To13(context), // Migration13To14(), //Auto: 14 -> 15 diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 8698b7736..00a186afe 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -18,6 +18,7 @@ import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 @@ -133,9 +134,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) - /* - Upgrade12To13(context).applyTo(db, 12) - */ + Upgrade12To13(context).migrate(db) db.close() runMigrationsAndValidate(14, Migration13To14()) @@ -1038,6 +1037,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) + Upgrade12To13(context).migrate(db) val pre14Statement = referencesStatement(db) val pre14Expected = "CONSTRAINT FK_FOLDER_CLOUD_ID_CLOUD_ENTITY FOREIGN KEY (FOLDER_CLOUD_ID) REFERENCES CLOUD_ENTITY(_id) ON DELETE SET NULL" @@ -1087,6 +1087,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) + Upgrade12To13(context).migrate(db) val pre14Statement = indexStatement(db) val pre14Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC) -- " @@ -1133,6 +1134,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) + Upgrade12To13(context).migrate(db) assertEquals(13, db.version) val pre14Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> @@ -1168,6 +1170,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler).migrate(db) Upgrade10To11().migrate(db) Upgrade11To12(sharedPreferencesHandler).migrate(db) + Upgrade12To13(context).migrate(db) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 3) // @@ -1254,6 +1257,7 @@ class UpgradeDatabaseTest { Upgrade9To10(sharedPreferencesHandler), Upgrade10To11(), Upgrade11To12(sharedPreferencesHandler), + Upgrade12To13(context), Migration13To14() ) } From e7778c67d5e0095ea67119fb6c0a027d30ac7d2a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 16 Oct 2024 03:17:26 +0200 Subject: [PATCH 113/120] Made tests provided by upstream v13 runnable Uncommented the tests Removed outdated calls to "Upgrade0To1" Replaced calls to removed method "applyTo" with calls to "migrate" --- .../data/db/UpgradeDatabaseTest.kt | 178 +++++++++--------- 1 file changed, 85 insertions(+), 93 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 00a186afe..47d954723 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -34,6 +34,7 @@ import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.domain.CloudType import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CredentialCryptor +import org.cryptomator.util.crypto.CryptoMode import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Assert @@ -716,21 +717,19 @@ class UpgradeDatabaseTest { Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent())) } - /* @Test fun upgrade12To13BaseTests() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -775,7 +774,7 @@ class UpgradeDatabaseTest { .integer("SHORTENING_THRESHOLD", 4) .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -814,18 +813,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13DropGoogleDriveUsernameInAccessToken() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -834,7 +832,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", "username") // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -844,18 +842,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13MovingAccessTokenToUrlInLocalStorage() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -863,7 +860,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", "testUrl3000") // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -874,18 +871,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13Dropbox() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -900,7 +896,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", accessTokenCiphertext) // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -912,18 +908,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13OneDrive() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -938,7 +933,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", accessTokenCiphertext) // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -949,18 +944,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13PCloud() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -976,7 +970,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -987,18 +981,17 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13Webdav() { - Upgrade0To1().applyTo(db, 0) - Upgrade1To2().applyTo(db, 1) - Upgrade2To3(context).applyTo(db, 2) - Upgrade3To4().applyTo(db, 3) - Upgrade4To5().applyTo(db, 4) - Upgrade5To6().applyTo(db, 5) - Upgrade6To7().applyTo(db, 6) - Upgrade7To8().applyTo(db, 7) - Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) - Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) - Upgrade10To11().applyTo(db, 10) - Upgrade11To12(sharedPreferencesHandler).applyTo(db, 11) + Upgrade1To2().migrate(db) + Upgrade2To3(context).migrate(db) + Upgrade3To4().migrate(db) + Upgrade4To5().migrate(db) + Upgrade5To6().migrate(db) + Upgrade6To7().migrate(db) + Upgrade7To8().migrate(db) + Upgrade8To9(sharedPreferencesHandler).migrate(db) + Upgrade9To10(sharedPreferencesHandler).migrate(db) + Upgrade10To11().migrate(db) + Upgrade11To12(sharedPreferencesHandler).migrate(db) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -1014,7 +1007,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .executeOn(db) - Upgrade12To13(context).applyTo(db, 12) + Upgrade12To13(context).migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -1022,7 +1015,6 @@ class UpgradeDatabaseTest { Assert.assertThat(it.getString(it.getColumnIndex("ACCESS_TOKEN_CRYPTO_MODE")), CoreMatchers.`is`(CryptoMode.GCM.name)) } } - */ @Test fun migrate13To15ForeignKeySideEffects() { //See: Migration13To14 From b663aafb3f0cb4e59569d8d88f1edc789e744475 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:45:09 +0200 Subject: [PATCH 114/120] Added "MigrationContainer" Replaced raw arrays and consecutive invocations of migrations with usages of "MigrationContainer" --- .../data/db/CorruptedDatabaseTest.kt | 32 +- .../data/db/UpgradeDatabaseTest.kt | 336 ++++-------------- .../org/cryptomator/data/db/DatabaseModule.kt | 48 +-- .../data/db/migrations/MigrationContainer.kt | 100 ++++++ 4 files changed, 192 insertions(+), 324 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/migrations/MigrationContainer.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index e5bccc473..d378d3fa8 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -1,7 +1,6 @@ package org.cryptomator.data.db import android.content.Context -import androidx.room.migration.Migration import androidx.room.util.readVersion import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper @@ -9,6 +8,7 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import org.cryptomator.data.db.migrations.MigrationContainer import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 @@ -67,28 +67,28 @@ class CorruptedDatabaseTest { @Test fun testOpenVersion0Database() { val databaseModule = DatabaseModule() - val migrations = arrayOf( - Upgrade1To2(), - Upgrade2To3(context), - Upgrade3To4(), - Upgrade4To5(), - Upgrade5To6(), - Upgrade6To7(), - Upgrade7To8(), - Upgrade8To9(sharedPreferencesHandler), - Upgrade9To10(sharedPreferencesHandler), - Upgrade10To11(), - Upgrade11To12(sharedPreferencesHandler), - Upgrade12To13(context), + val migrationContainer = MigrationContainer( + Upgrade1To2(), // + Upgrade2To3(context), // + Upgrade3To4(), // + Upgrade4To5(), // + Upgrade5To6(), // + Upgrade6To7(), // + Upgrade7To8(), // + Upgrade8To9(sharedPreferencesHandler), // + Upgrade9To10(sharedPreferencesHandler), // + Upgrade10To11(), // + Upgrade11To12(sharedPreferencesHandler), // + Upgrade12To13(context), // // - Migration13To14(), + Migration13To14(), // //Auto: 14 -> 15 ) createVersion0Database(context, TEST_DB) databaseModule.provideInternalCryptomatorDatabase( context, - migrations, + migrationContainer.getPath(1).toTypedArray(), { templateDbStream }, openHelperFactory, TEST_DB diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 47d954723..6ee625f4e 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -15,6 +15,7 @@ import com.google.common.base.Optional import org.cryptomator.data.db.CryptomatorAssert.assertCursorEquals import org.cryptomator.data.db.CryptomatorAssert.assertIsUUID import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled +import org.cryptomator.data.db.migrations.MigrationContainer import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 @@ -69,6 +70,7 @@ class UpgradeDatabaseTest { it.mark(it.available()) } + private lateinit var migrationContainer: MigrationContainer private lateinit var openHelper: SupportSQLiteOpenHelper private lateinit var db: SupportSQLiteDatabase @@ -91,6 +93,24 @@ class UpgradeDatabaseTest { Files.copy(templateDbStream, dbFile.toPath()) } + migrationContainer = MigrationContainer( + Upgrade1To2(), // + Upgrade2To3(context), // + Upgrade3To4(), // + Upgrade4To5(), // + Upgrade5To6(), // + Upgrade6To7(), // + Upgrade7To8(), // + Upgrade8To9(sharedPreferencesHandler), // + Upgrade9To10(sharedPreferencesHandler), // + Upgrade10To11(), // + Upgrade11To12(sharedPreferencesHandler), // + Upgrade12To13(context), // + // + Migration13To14(), // + //Auto: 14 -> 15 + ) + val config = SupportSQLiteOpenHelper.Configuration.builder(context) // .name(TEST_DB) // .callback(object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { @@ -124,18 +144,7 @@ class UpgradeDatabaseTest { @Test fun upgradeAll() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) - Upgrade12To13(context).migrate(db) + migrationContainer.applyPath(db, 1, 13) db.close() runMigrationsAndValidate(14, Migration13To14()) @@ -149,7 +158,7 @@ class UpgradeDatabaseTest { @Test fun upgrade2To3() { - Upgrade1To2().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 2) val url = "url" val username = "username" @@ -174,7 +183,7 @@ class UpgradeDatabaseTest { context.getSharedPreferences("com.microsoft.live", Context.MODE_PRIVATE).edit().putString("refresh_token", accessToken).commit() - Upgrade2To3(context).migrate(db) + testedUpgrade.migrate(db) checkUpgrade2to3ResultForCloud("DROPBOX", accessToken, url, username, webdavCertificate) checkUpgrade2to3ResultForCloud("ONEDRIVE", accessToken, url, username, webdavCertificate) @@ -194,8 +203,7 @@ class UpgradeDatabaseTest { @Test fun upgrade3To4() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 3) val ids = arrayOf("10", "20", "31", "32", "51") @@ -210,7 +218,7 @@ class UpgradeDatabaseTest { .executeOn(db) } - Upgrade3To4().migrate(db) + testedUpgrade.migrate(db) Sql.query("VAULT_ENTITY").where("CLOUD_TYPE", Sql.eq(CloudType.DROPBOX.name)).executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(ids.size)) @@ -228,9 +236,7 @@ class UpgradeDatabaseTest { @Test fun upgrade4To5() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 4) val cloudId = 15 val cloudUrl = "url" @@ -263,7 +269,7 @@ class UpgradeDatabaseTest { .integer("POSITION", position) // .executeOn(db) - Upgrade4To5().migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("TYPE", Sql.eq(CloudType.WEBDAV.name)).executeOn(db).use { it.moveToFirst() @@ -289,10 +295,7 @@ class UpgradeDatabaseTest { @Test fun upgrade5To6() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 5) val cloudId = 15 val cloudUrl = "url" @@ -325,7 +328,7 @@ class UpgradeDatabaseTest { .integer("POSITION", position) // .executeOn(db) - Upgrade5To6().migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("TYPE", Sql.eq(CloudType.WEBDAV.name)).executeOn(db).use { it.moveToFirst() @@ -351,11 +354,7 @@ class UpgradeDatabaseTest { @Test fun upgrade6To7() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 6) val licenseToken = "licenseToken" val releaseNote = "releaseNote" @@ -371,7 +370,7 @@ class UpgradeDatabaseTest { .set("URL_TO_RELEASE_NOTE", Sql.toString(urlReleaseNote)) // .executeOn(db) - Upgrade6To7().migrate(db) + testedUpgrade.migrate(db) Sql.query("UPDATE_CHECK_ENTITY").executeOn(db).use { it.moveToFirst() @@ -386,11 +385,7 @@ class UpgradeDatabaseTest { @Test fun upgrade6To7DueToSQLiteExceptionThrown() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 6) val licenseToken = "licenseToken" @@ -414,7 +409,7 @@ class UpgradeDatabaseTest { .optionalText("URL_TO_RELEASE_NOTE") // .executeOn(db) - Upgrade6To7().tryToRecoverFromSQLiteException(db) + testedUpgrade.tryToRecoverFromSQLiteException(db) Sql.query("UPDATE_CHECK_ENTITY").executeOn(db).use { it.moveToFirst() @@ -429,12 +424,7 @@ class UpgradeDatabaseTest { @Test fun upgrade7To8() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 7) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -462,7 +452,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(5)) } - Upgrade7To8().migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(4)) @@ -475,31 +465,18 @@ class UpgradeDatabaseTest { @Test fun upgrade8To9() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 8) sharedPreferencesHandler.setBetaScreenDialogAlreadyShown(true) - Upgrade8To9(sharedPreferencesHandler).migrate(db) + testedUpgrade.migrate(db) Assert.assertThat(sharedPreferencesHandler.isBetaModeAlreadyShown(), CoreMatchers.`is`(false)) } @Test fun upgrade9To10() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 9) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -537,7 +514,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(5)) } - Upgrade9To10(sharedPreferencesHandler).migrate(db) + testedUpgrade.migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -552,15 +529,7 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11EmptyOnedriveCloudRemovesCloud() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 10) Sql.insertInto("VAULT_ENTITY") // .integer("_id", 25) // @@ -576,7 +545,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(3)) } - Upgrade10To11().migrate(db) + testedUpgrade.migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -601,15 +570,7 @@ class UpgradeDatabaseTest { @Test fun upgrade10To11UsedOnedriveCloudPreservesCloud() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 10) Sql.insertInto("VAULT_ENTITY") // .integer("_id", 25) // @@ -634,7 +595,7 @@ class UpgradeDatabaseTest { Assert.assertThat(it.count, CoreMatchers.`is`(3)) } - Upgrade10To11().migrate(db) + testedUpgrade.migrate(db) Sql.query("VAULT_ENTITY").executeOn(db).use { Assert.assertThat(it.count, CoreMatchers.`is`(1)) @@ -659,77 +620,40 @@ class UpgradeDatabaseTest { @Test fun upgrade11To12IfOldDefaultSet() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 11) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(7)) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + testedUpgrade.migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1))) } @Test fun upgrade11To12MonthlySet() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 11) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.of(30)) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + testedUpgrade.migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.of(1))) } @Test fun upgrade11To12MonthlyNever() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 11) sharedPreferencesHandler.setUpdateIntervalInDays(Optional.absent()) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + testedUpgrade.migrate(db) Assert.assertThat(sharedPreferencesHandler.updateIntervalInDays(), CoreMatchers.`is`(Optional.absent())) } @Test fun upgrade12To13BaseTests() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -774,7 +698,7 @@ class UpgradeDatabaseTest { .integer("SHORTENING_THRESHOLD", 4) .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -813,17 +737,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13DropGoogleDriveUsernameInAccessToken() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -832,7 +746,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", "username") // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -842,17 +756,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13MovingAccessTokenToUrlInLocalStorage() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 15) // @@ -860,7 +764,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", "testUrl3000") // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -871,17 +775,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13Dropbox() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -896,7 +790,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", accessTokenCiphertext) // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -908,17 +802,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13OneDrive() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -933,7 +817,7 @@ class UpgradeDatabaseTest { .text("ACCESS_TOKEN", accessTokenCiphertext) // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -944,17 +828,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13PCloud() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -970,7 +844,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -981,17 +855,7 @@ class UpgradeDatabaseTest { @Test fun upgrade12To13Webdav() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 12) val gcmCryptor = CredentialCryptor.getInstance(context, CryptoMode.GCM) val cbcCryptor = CredentialCryptor.getInstance(context, CryptoMode.CBC) @@ -1007,7 +871,7 @@ class UpgradeDatabaseTest { .text("URL", "url") // .executeOn(db) - Upgrade12To13(context).migrate(db) + testedUpgrade.migrate(db) Sql.query("CLOUD_ENTITY").where("_id", Sql.eq(15)).executeOn(db).use { it.moveToFirst() @@ -1018,18 +882,7 @@ class UpgradeDatabaseTest { @Test fun migrate13To15ForeignKeySideEffects() { //See: Migration13To14 - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) - Upgrade12To13(context).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 13) val pre14Statement = referencesStatement(db) val pre14Expected = "CONSTRAINT FK_FOLDER_CLOUD_ID_CLOUD_ENTITY FOREIGN KEY (FOLDER_CLOUD_ID) REFERENCES CLOUD_ENTITY(_id) ON DELETE SET NULL" @@ -1037,7 +890,7 @@ class UpgradeDatabaseTest { assertTrue("Expected \".*$pre14Expected.*\", got \"$pre14Statement\"", pre14Statement.contains(pre14Expected)) db.close() - runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> + runMigrationsAndValidate(14, testedUpgrade).also { migratedDb -> val statement = referencesStatement(migratedDb) assertEquals(pre14Statement, statement) } @@ -1068,18 +921,7 @@ class UpgradeDatabaseTest { @Test fun migrate13To15IndexSideEffects() { //See: Migration13To14 - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) - Upgrade12To13(context).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 13) val pre14Statement = indexStatement(db) val pre14Expected = "CREATE UNIQUE INDEX \"IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID\" ON \"VAULT_ENTITY\" (\"FOLDER_PATH\" ASC,\"FOLDER_CLOUD_ID\" ASC) -- " @@ -1088,7 +930,7 @@ class UpgradeDatabaseTest { assertIsUUID(pre14Statement.substring(pre14Statement.length - UUID_LENGTH)) db.close() - runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> + runMigrationsAndValidate(14, testedUpgrade).also { migratedDb -> val statement = indexStatement(migratedDb) assertEquals(pre14Statement, statement) } @@ -1115,18 +957,7 @@ class UpgradeDatabaseTest { @Test fun migrate13To14() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) - Upgrade12To13(context).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 13) assertEquals(13, db.version) val pre14Tables: Map = listOf("CLOUD_ENTITY", "UPDATE_CHECK_ENTITY", "VAULT_ENTITY").associateWith { tableName -> @@ -1135,7 +966,7 @@ class UpgradeDatabaseTest { } db.close() - runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> + runMigrationsAndValidate(14, testedUpgrade).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) assertEquals(14, migratedDb.version) @@ -1151,18 +982,7 @@ class UpgradeDatabaseTest { @Test fun migrate13To14WithData() { - Upgrade1To2().migrate(db) - Upgrade2To3(context).migrate(db) - Upgrade3To4().migrate(db) - Upgrade4To5().migrate(db) - Upgrade5To6().migrate(db) - Upgrade6To7().migrate(db) - Upgrade7To8().migrate(db) - Upgrade8To9(sharedPreferencesHandler).migrate(db) - Upgrade9To10(sharedPreferencesHandler).migrate(db) - Upgrade10To11().migrate(db) - Upgrade11To12(sharedPreferencesHandler).migrate(db) - Upgrade12To13(context).migrate(db) + val testedUpgrade = migrationContainer.applyPathAndReturnNext(db, 1, 13) Sql.insertInto("CLOUD_ENTITY") // .integer("_id", 3) // @@ -1215,7 +1035,7 @@ class UpgradeDatabaseTest { } db.close() - runMigrationsAndValidate(14, Migration13To14()).also { migratedDb -> + runMigrationsAndValidate(14, testedUpgrade).also { migratedDb -> assertTrue(migratedDb.hasRoomMasterTable) assertEquals(14, migratedDb.version) @@ -1238,19 +1058,7 @@ class UpgradeDatabaseTest { db.close() runMigrationsAndValidate( 14, - Upgrade1To2(), - Upgrade2To3(context), - Upgrade3To4(), - Upgrade4To5(), - Upgrade5To6(), - Upgrade6To7(), - Upgrade7To8(), - Upgrade8To9(sharedPreferencesHandler), - Upgrade9To10(sharedPreferencesHandler), - Upgrade10To11(), - Upgrade11To12(sharedPreferencesHandler), - Upgrade12To13(context), - Migration13To14() + *migrationContainer.getPath(1, 14).toTypedArray() ) } } \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt index 33413637c..6427263f6 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseModule.kt @@ -7,19 +7,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper import org.cryptomator.data.db.SQLiteCacheControl.asCacheControlled -import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 -import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 -import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 -import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 -import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 -import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 -import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 -import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 -import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 -import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 -import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 -import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 -import org.cryptomator.data.db.migrations.manual.Migration13To14 +import org.cryptomator.data.db.migrations.MigrationContainer import org.cryptomator.data.db.templating.DbTemplateComponent import org.cryptomator.util.ThreadUtil import org.cryptomator.util.named @@ -137,37 +125,9 @@ class DatabaseModule { @Singleton @Provides @DbInternal - internal fun provideMigrations( - upgrade1To2: Upgrade1To2, // - upgrade2To3: Upgrade2To3, // - upgrade3To4: Upgrade3To4, // - upgrade4To5: Upgrade4To5, // - upgrade5To6: Upgrade5To6, // - upgrade6To7: Upgrade6To7, // - upgrade7To8: Upgrade7To8, // - upgrade8To9: Upgrade8To9, // - upgrade9To10: Upgrade9To10, // - upgrade10To11: Upgrade10To11, // - upgrade11To12: Upgrade11To12, // - upgrade12To13: Upgrade12To13, // - // - migration13To14: Migration13To14, // - ): Array = arrayOf( - upgrade1To2, - upgrade2To3, - upgrade5To6, - upgrade3To4, - upgrade4To5, - upgrade6To7, - upgrade7To8, - upgrade8To9, - upgrade9To10, - upgrade10To11, - upgrade11To12, - upgrade12To13, - // - migration13To14, - ) + internal fun provideMigrations(migrationContainer: MigrationContainer): Array { + return migrationContainer.getPath(1).toTypedArray() + } } object DatabaseCallback : RoomDatabase.Callback() { diff --git a/data/src/main/java/org/cryptomator/data/db/migrations/MigrationContainer.kt b/data/src/main/java/org/cryptomator/data/db/migrations/MigrationContainer.kt new file mode 100644 index 000000000..a1f6b17cf --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/migrations/MigrationContainer.kt @@ -0,0 +1,100 @@ +package org.cryptomator.data.db.migrations + +import androidx.sqlite.db.SupportSQLiteDatabase +import org.cryptomator.data.db.DatabaseMigration +import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 +import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 +import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 +import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 +import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 +import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 +import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 +import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 +import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 +import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 +import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 +import org.cryptomator.data.db.migrations.manual.Migration13To14 +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class MigrationContainer private constructor(private val migrations: List) { + + @Inject + internal constructor( + upgrade1To2: Upgrade1To2, // + upgrade2To3: Upgrade2To3, // + upgrade3To4: Upgrade3To4, // + upgrade4To5: Upgrade4To5, // + upgrade5To6: Upgrade5To6, // + upgrade6To7: Upgrade6To7, // + upgrade7To8: Upgrade7To8, // + upgrade8To9: Upgrade8To9, // + upgrade9To10: Upgrade9To10, // + upgrade10To11: Upgrade10To11, // + upgrade11To12: Upgrade11To12, // + upgrade12To13: Upgrade12To13, // + // + migration13To14: Migration13To14, // + ) : this( + validateMigrations( + upgrade1To2, // + upgrade2To3, // + upgrade3To4, // + upgrade4To5, // + upgrade5To6, // + upgrade6To7, // + upgrade7To8, // + upgrade8To9, // + upgrade9To10, // + upgrade10To11, // + upgrade11To12, // + upgrade12To13, // + // + migration13To14, // + ) + ) + + internal fun getPath(oldVersion: Int): List { + return getPath(oldVersion, migrations.size + 1) + } + + internal fun getPath(oldVersion: Int, newVersion: Int): List { + require(oldVersion in 1.. applyPathAndReturnNext(database: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int): T { + val nextMigration = migrations.getOrNull(newVersion - 1) ?: throw IllegalArgumentException("No migration from version $newVersion to ${newVersion + 1}") + require(nextMigration is T) + applyPath(database, oldVersion, newVersion) + return nextMigration + } + + internal fun applyPathAndReturnNext(database: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int): DatabaseMigration { + return applyPathAndReturnNext(database, oldVersion, newVersion) + } +} + +private fun validateMigrations(vararg migrations: DatabaseMigration): List { + require(migrations.isNotEmpty()) + return migrations.asSequence().onEachIndexed { index, migration -> + require(migration.startVersion == (index + 1) && Math.addExact(migration.startVersion, 1) == migration.endVersion) { // + "Illegal migration configuration" + } + }.toCollection(ArrayList(migrations.size)) +} \ No newline at end of file From 0b164f89b395bff2ddcb1a7dda06ce0ae598b07a Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 14 Oct 2024 21:10:18 +0200 Subject: [PATCH 115/120] Refactored "CorruptedDatabaseTest" Moved initialization/property for "MigrationContainer" from "testOpenVersion0Database" to "setup"/class body Cleaned up "testOpenVersion0Database" --- .../data/db/CorruptedDatabaseTest.kt | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index d378d3fa8..a00c2f08d 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -47,6 +47,8 @@ class CorruptedDatabaseTest { it.mark(it.available()) } + private lateinit var migrationContainer: MigrationContainer + @Before fun setup() { context.getDatabasePath(TEST_DB).also { dbFile -> @@ -56,18 +58,8 @@ class CorruptedDatabaseTest { dbFile.delete() } } - } - - @After - fun tearDown() { - context.getDatabasePath(TEST_DB).delete() - templateDbStream.reset() - } - @Test - fun testOpenVersion0Database() { - val databaseModule = DatabaseModule() - val migrationContainer = MigrationContainer( + migrationContainer = MigrationContainer( Upgrade1To2(), // Upgrade2To3(context), // Upgrade3To4(), // @@ -84,14 +76,23 @@ class CorruptedDatabaseTest { Migration13To14(), // //Auto: 14 -> 15 ) + } + @After + fun tearDown() { + context.getDatabasePath(TEST_DB).delete() + templateDbStream.reset() + } + + @Test + fun testOpenVersion0Database() { createVersion0Database(context, TEST_DB) - databaseModule.provideInternalCryptomatorDatabase( - context, - migrationContainer.getPath(1).toTypedArray(), - { templateDbStream }, - openHelperFactory, - TEST_DB + DatabaseModule().provideInternalCryptomatorDatabase( // + context, // + migrationContainer.getPath(1).toTypedArray(), // + { templateDbStream }, // + openHelperFactory, // + TEST_DB // ).useFinally({ db -> db.compileStatement("SELECT count(*) FROM `sqlite_master` WHERE `name` = 'CLOUD_ENTITY'").use { statement -> require(statement.simpleQueryForLong() == 1L) From 70cb60a154bf426f686e95635cc6ef241bda4bf2 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 14 Oct 2024 21:36:22 +0200 Subject: [PATCH 116/120] Added "testOpenVersion0DatabaseVerifyStreamAccessed" --- .../data/db/CorruptedDatabaseTest.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index a00c2f08d..fb6e196d5 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -27,6 +27,7 @@ import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.data.util.useFinally import org.cryptomator.util.SharedPreferencesHandler import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -99,6 +100,35 @@ class CorruptedDatabaseTest { } }, finallyBlock = CryptomatorDatabase::close) } + + @Test + fun testOpenVersion0DatabaseVerifyStreamAccessed() { + var step = 0 + val templateStreamCallable = { + assertEquals(step++, 0) + templateDbStream + } + val listener = object : InterceptorOpenHelperListener { + override fun onWritableDatabaseCalled() { + assertEquals(step++, 1) + } + } + + createVersion0Database(context, TEST_DB) + DatabaseModule().provideInternalCryptomatorDatabase( // + context, // + migrationContainer.getPath(1).toTypedArray(), // + templateStreamCallable, // + InterceptorOpenHelperFactory(openHelperFactory, listener), // + TEST_DB // + ).useFinally({ db -> + assertEquals(step++, 2) + db.compileStatement("SELECT count(*) FROM `sqlite_master` WHERE `name` = 'CLOUD_ENTITY'").use { statement -> + require(statement.simpleQueryForLong() == 1L) + } + }, finallyBlock = CryptomatorDatabase::close) + assertEquals(step++, 3) + } } @Suppress("serial") @@ -133,4 +163,37 @@ private fun initVersion0Database(openHelper: SupportSQLiteOpenHelper): Nothing { openHelper.writableDatabase.use { throw IllegalStateException("Creating a v0 database requires throwing an exception during creation (got ${it.version})") } +} + +private class InterceptorOpenHelperFactory( + private val delegate: SupportSQLiteOpenHelper.Factory, // + private val listener: InterceptorOpenHelperListener +) : SupportSQLiteOpenHelper.Factory { + + override fun create( + configuration: SupportSQLiteOpenHelper.Configuration + ): SupportSQLiteOpenHelper { + return InterceptorOpenHelper(delegate.create(configuration), listener) + } +} + +private class InterceptorOpenHelper( + private val delegate: SupportSQLiteOpenHelper, // + private val listener: InterceptorOpenHelperListener +) : SupportSQLiteOpenHelper by delegate { + + override val writableDatabase: SupportSQLiteDatabase + get() { + listener.onWritableDatabaseCalled() + return delegate.writableDatabase + } + + override val readableDatabase: SupportSQLiteDatabase + get() = throw AssertionError() +} + +private interface InterceptorOpenHelperListener { + + fun onWritableDatabaseCalled() + } \ No newline at end of file From 8251fc90b774409418aae617f7ef85693beb674f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 14 Oct 2024 21:51:59 +0200 Subject: [PATCH 117/120] Added "testOpenDatabaseWithRecovery" Replaced "openHelperFactory" property with method --- .../data/db/CorruptedDatabaseTest.kt | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index fb6e196d5..47c50665c 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -26,11 +26,16 @@ import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.data.util.useFinally import org.cryptomator.util.SharedPreferencesHandler +import org.hamcrest.CoreMatchers.instanceOf +import org.hamcrest.MatcherAssert.assertThat import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import java.io.IOException private const val TEST_DB = "corruption-test" @@ -40,7 +45,6 @@ class CorruptedDatabaseTest { private val context = InstrumentationRegistry.getInstrumentation().context private val sharedPreferencesHandler = SharedPreferencesHandler(context) - private val openHelperFactory = DatabaseOpenHelperFactory { throw IllegalStateException() } private val templateDbStream = DbTemplateModule().let { it.provideDbTemplateStream(it.provideConfiguration(TemplateDatabaseContext(context))) }.also { @@ -92,7 +96,7 @@ class CorruptedDatabaseTest { context, // migrationContainer.getPath(1).toTypedArray(), // { templateDbStream }, // - openHelperFactory, // + openHelperFactory(), // TEST_DB // ).useFinally({ db -> db.compileStatement("SELECT count(*) FROM `sqlite_master` WHERE `name` = 'CLOUD_ENTITY'").use { statement -> @@ -119,7 +123,7 @@ class CorruptedDatabaseTest { context, // migrationContainer.getPath(1).toTypedArray(), // templateStreamCallable, // - InterceptorOpenHelperFactory(openHelperFactory, listener), // + InterceptorOpenHelperFactory(openHelperFactory(), listener), // TEST_DB // ).useFinally({ db -> assertEquals(step++, 2) @@ -129,6 +133,45 @@ class CorruptedDatabaseTest { }, finallyBlock = CryptomatorDatabase::close) assertEquals(step++, 3) } + + @Test + fun testOpenDatabaseWithRecovery() { + var step = 0 + val templateStreamCallable = { + assertEquals(step++, 0) + throw IOException() + } + val listener = object : InterceptorOpenHelperListener { + override fun onWritableDatabaseCalled() { + assertEquals(step++, 1) + } + + override fun onWritableDatabaseThrew(exc: Exception): Exception { + assertEquals(step++, 3) + assertThat(exc, instanceOf(UnsupportedOperationException::class.java)) + return WrappedException(exc) + } + } + val openHelperFactory = openHelperFactory { + assertEquals(step++, 2) + } + + createVersion0Database(context, TEST_DB) + assertThrows(WrappedException::class.java) { + DatabaseModule().provideInternalCryptomatorDatabase( // + context, // + migrationContainer.getPath(1).toTypedArray(), // + templateStreamCallable, // + InterceptorOpenHelperFactory(openHelperFactory, listener), // + TEST_DB // + ).useFinally({ _ -> + fail("Database initialization must throw") + }, finallyBlock = CryptomatorDatabase::close) + }.also { + assertThat(it.cause, instanceOf(UnsupportedOperationException::class.java)) + } + assertEquals(step++, 4) + } } @Suppress("serial") @@ -165,6 +208,12 @@ private fun initVersion0Database(openHelper: SupportSQLiteOpenHelper): Nothing { } } +private fun openHelperFactory( + invalidationCallback: () -> Unit = { throw IllegalStateException() } +): DatabaseOpenHelperFactory { + return DatabaseOpenHelperFactory(invalidationCallback) +} + private class InterceptorOpenHelperFactory( private val delegate: SupportSQLiteOpenHelper.Factory, // private val listener: InterceptorOpenHelperListener @@ -185,7 +234,11 @@ private class InterceptorOpenHelper( override val writableDatabase: SupportSQLiteDatabase get() { listener.onWritableDatabaseCalled() - return delegate.writableDatabase + try { + return delegate.writableDatabase + } catch (exc: Exception) { + throw listener.onWritableDatabaseThrew(exc) + } } override val readableDatabase: SupportSQLiteDatabase @@ -195,5 +248,9 @@ private class InterceptorOpenHelper( private interface InterceptorOpenHelperListener { fun onWritableDatabaseCalled() + fun onWritableDatabaseThrew(exc: Exception): Exception = exc -} \ No newline at end of file +} + +@Suppress("serial") +class WrappedException(cause: Exception) : Exception(cause) \ No newline at end of file From 998a4768d2e8fa5f0c2df5fd6024f934a519cde8 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Wed, 16 Oct 2024 05:23:31 +0200 Subject: [PATCH 118/120] Consolidated common test-logic to "DatabaseTests.kt" Moved logic for configuring a test database, a "MigrationContainer" and creating a v0 database to "DatabaseTests.kt" *This commit is related to issue #529 [1]* [1] https://github.com/cryptomator/android/issues/529 --- .../data/db/CorruptedDatabaseTest.kt | 76 ++----------- .../org/cryptomator/data/db/DatabaseTests.kt | 100 ++++++++++++++++++ .../data/db/UpgradeDatabaseTest.kt | 40 +------ 3 files changed, 109 insertions(+), 107 deletions(-) create mode 100644 data/src/androidTest/java/org/cryptomator/data/db/DatabaseTests.kt diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 47c50665c..4a6b8e2e1 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -1,27 +1,11 @@ package org.cryptomator.data.db -import android.content.Context -import androidx.room.util.readVersion import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import org.cryptomator.data.db.migrations.MigrationContainer -import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 -import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 -import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 -import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 -import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 -import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 -import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 -import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 -import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 -import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 -import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 -import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 -import org.cryptomator.data.db.migrations.manual.Migration13To14 import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext import org.cryptomator.data.util.useFinally @@ -64,23 +48,7 @@ class CorruptedDatabaseTest { } } - migrationContainer = MigrationContainer( - Upgrade1To2(), // - Upgrade2To3(context), // - Upgrade3To4(), // - Upgrade4To5(), // - Upgrade5To6(), // - Upgrade6To7(), // - Upgrade7To8(), // - Upgrade8To9(sharedPreferencesHandler), // - Upgrade9To10(sharedPreferencesHandler), // - Upgrade10To11(), // - Upgrade11To12(sharedPreferencesHandler), // - Upgrade12To13(context), // - // - Migration13To14(), // - //Auto: 14 -> 15 - ) + migrationContainer = createMigrationContainer(context, sharedPreferencesHandler) } @After @@ -89,9 +57,13 @@ class CorruptedDatabaseTest { templateDbStream.reset() } + private fun createVersion0Database() { + createVersion0Database(context, TEST_DB) + } + @Test fun testOpenVersion0Database() { - createVersion0Database(context, TEST_DB) + createVersion0Database() DatabaseModule().provideInternalCryptomatorDatabase( // context, // migrationContainer.getPath(1).toTypedArray(), // @@ -118,7 +90,7 @@ class CorruptedDatabaseTest { } } - createVersion0Database(context, TEST_DB) + createVersion0Database() DatabaseModule().provideInternalCryptomatorDatabase( // context, // migrationContainer.getPath(1).toTypedArray(), // @@ -174,40 +146,6 @@ class CorruptedDatabaseTest { } } -@Suppress("serial") -private class InterruptCreationException : Exception() - -private fun createVersion0Database(context: Context, databaseName: String) { - val config = SupportSQLiteOpenHelper.Configuration.builder(context) // - .name(databaseName) // - .callback(object : SupportSQLiteOpenHelper.Callback(1) { - override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - assertedWalEnabledStatus = false // - ) - - override fun onCreate(db: SupportSQLiteDatabase) = throw InterruptCreationException() - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = throw IllegalStateException() - }).build() - - FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> - openHelper.setWriteAheadLoggingEnabled(false) - try { - //The "use" block in "initVersion0Database" should not be reached, let alone finished; ... - initVersion0Database(openHelper) - } catch (e: InterruptCreationException) { - //... instead, the creation of the database should be interrupted by the InterruptCreationException thrown by "onCreate", - //so that this catch block is called and the database remains in version 0. - require(readVersion(context.getDatabasePath(databaseName)) == 0) - } - } -} - -private fun initVersion0Database(openHelper: SupportSQLiteOpenHelper): Nothing { - openHelper.writableDatabase.use { - throw IllegalStateException("Creating a v0 database requires throwing an exception during creation (got ${it.version})") - } -} - private fun openHelperFactory( invalidationCallback: () -> Unit = { throw IllegalStateException() } ): DatabaseOpenHelperFactory { diff --git a/data/src/androidTest/java/org/cryptomator/data/db/DatabaseTests.kt b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseTests.kt new file mode 100644 index 000000000..8e8c4064c --- /dev/null +++ b/data/src/androidTest/java/org/cryptomator/data/db/DatabaseTests.kt @@ -0,0 +1,100 @@ +package org.cryptomator.data.db + +import android.content.Context +import androidx.room.util.readVersion +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import org.cryptomator.data.db.migrations.MigrationContainer +import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 +import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 +import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 +import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 +import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 +import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 +import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 +import org.cryptomator.data.db.migrations.legacy.Upgrade5To6 +import org.cryptomator.data.db.migrations.legacy.Upgrade6To7 +import org.cryptomator.data.db.migrations.legacy.Upgrade7To8 +import org.cryptomator.data.db.migrations.legacy.Upgrade8To9 +import org.cryptomator.data.db.migrations.legacy.Upgrade9To10 +import org.cryptomator.data.db.migrations.manual.Migration13To14 +import org.cryptomator.util.SharedPreferencesHandler +import org.junit.Assert.assertEquals +import org.junit.Assert.fail + +private const val LATEST_LEGACY_MIGRATION = 13 + +internal fun configureTestDatabase(context: Context, databaseName: String): SupportSQLiteOpenHelper.Configuration { + return SupportSQLiteOpenHelper.Configuration.builder(context) // + .name(databaseName) // + .callback(object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + assertedWalEnabledStatus = false // + ) + + override fun onCreate(db: SupportSQLiteDatabase) { + fail("Database should not be created, but copied from template") + } + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + assertEquals(1, oldVersion) + assertEquals(LATEST_LEGACY_MIGRATION, newVersion) + } + }).build() +} + +internal fun createVersion0Database(context: Context, databaseName: String) { + val config = SupportSQLiteOpenHelper.Configuration.builder(context) // + .name(databaseName) // + .callback(object : SupportSQLiteOpenHelper.Callback(1) { + override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // + assertedWalEnabledStatus = false // + ) + + override fun onCreate(db: SupportSQLiteDatabase) = throw InterruptCreationException() + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) = throw IllegalStateException() + }).build() + + FrameworkSQLiteOpenHelperFactory().create(config).use { openHelper -> + openHelper.setWriteAheadLoggingEnabled(false) + try { + //The "use" block in "initVersion0Database" should not be reached, let alone finished; ... + initVersion0Database(openHelper) + } catch (e: InterruptCreationException) { + //... instead, the creation of the database should be interrupted by the InterruptCreationException thrown by "onCreate", + //so that this catch block is called and the database remains in version 0. + require(readVersion(context.getDatabasePath(databaseName)) == 0) + } + } +} + +@Suppress("serial") +private class InterruptCreationException : Exception() + +private fun initVersion0Database(openHelper: SupportSQLiteOpenHelper): Nothing { + openHelper.writableDatabase.use { + throw IllegalStateException("Creating a v0 database requires throwing an exception during creation (got ${it.version})") + } +} + +internal fun createMigrationContainer( + context: Context, // + sharedPreferencesHandler: SharedPreferencesHandler +) = MigrationContainer( + Upgrade1To2(), // + Upgrade2To3(context), // + Upgrade3To4(), // + Upgrade4To5(), // + Upgrade5To6(), // + Upgrade6To7(), // + Upgrade7To8(), // + Upgrade8To9(sharedPreferencesHandler), // + Upgrade9To10(sharedPreferencesHandler), // + Upgrade10To11(), // + Upgrade11To12(sharedPreferencesHandler), // + Upgrade12To13(context), // + // + Migration13To14(), // + //Auto: 14 -> 15 +) \ No newline at end of file diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 6ee625f4e..51d182fe9 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -20,7 +20,6 @@ import org.cryptomator.data.db.migrations.Sql import org.cryptomator.data.db.migrations.legacy.Upgrade10To11 import org.cryptomator.data.db.migrations.legacy.Upgrade11To12 import org.cryptomator.data.db.migrations.legacy.Upgrade12To13 -import org.cryptomator.data.db.migrations.legacy.Upgrade1To2 import org.cryptomator.data.db.migrations.legacy.Upgrade2To3 import org.cryptomator.data.db.migrations.legacy.Upgrade3To4 import org.cryptomator.data.db.migrations.legacy.Upgrade4To5 @@ -42,7 +41,6 @@ import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.Before import org.junit.Rule import org.junit.Test @@ -51,7 +49,6 @@ import java.io.IOException import java.nio.file.Files private const val TEST_DB = "migration-test" -private const val LATEST_LEGACY_MIGRATION = 13 private const val UUID_LENGTH = 36 @@ -93,42 +90,9 @@ class UpgradeDatabaseTest { Files.copy(templateDbStream, dbFile.toPath()) } - migrationContainer = MigrationContainer( - Upgrade1To2(), // - Upgrade2To3(context), // - Upgrade3To4(), // - Upgrade4To5(), // - Upgrade5To6(), // - Upgrade6To7(), // - Upgrade7To8(), // - Upgrade8To9(sharedPreferencesHandler), // - Upgrade9To10(sharedPreferencesHandler), // - Upgrade10To11(), // - Upgrade11To12(sharedPreferencesHandler), // - Upgrade12To13(context), // - // - Migration13To14(), // - //Auto: 14 -> 15 - ) - - val config = SupportSQLiteOpenHelper.Configuration.builder(context) // - .name(TEST_DB) // - .callback(object : SupportSQLiteOpenHelper.Callback(LATEST_LEGACY_MIGRATION) { - override fun onConfigure(db: SupportSQLiteDatabase) = db.applyDefaultConfiguration( // - assertedWalEnabledStatus = false // - ) - - override fun onCreate(db: SupportSQLiteDatabase) { - fail("Database should not be created, but copied from template") - } - - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - assertEquals(1, oldVersion) - assertEquals(LATEST_LEGACY_MIGRATION, newVersion) - } - }).build() + migrationContainer = createMigrationContainer(context, sharedPreferencesHandler) - openHelper = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(config) + openHelper = FrameworkSQLiteOpenHelperFactory().asCacheControlled().create(configureTestDatabase(context, TEST_DB)) openHelper.setWriteAheadLoggingEnabled(false) db = openHelper.writableDatabase } From 6ed2b426ba4c5ecaf231902f169328ff9dec5e8c Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sun, 20 Oct 2024 00:27:03 +0200 Subject: [PATCH 119/120] Added "assertOrder" Added "assertOrder" to simplify orders that contain multiple calls to the same assertion --- .../data/db/CorruptedDatabaseTest.kt | 25 ++++++----- .../data/db/CryptomatorAssert.java | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index 4a6b8e2e1..d499c38de 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -5,6 +5,8 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import org.cryptomator.data.db.CryptomatorAssert.Order +import org.cryptomator.data.db.CryptomatorAssert.assertOrder import org.cryptomator.data.db.migrations.MigrationContainer import org.cryptomator.data.db.templating.DbTemplateModule import org.cryptomator.data.db.templating.TemplateDatabaseContext @@ -13,7 +15,6 @@ import org.cryptomator.util.SharedPreferencesHandler import org.hamcrest.CoreMatchers.instanceOf import org.hamcrest.MatcherAssert.assertThat import org.junit.After -import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Assert.fail import org.junit.Before @@ -79,14 +80,14 @@ class CorruptedDatabaseTest { @Test fun testOpenVersion0DatabaseVerifyStreamAccessed() { - var step = 0 + val order = Order() val templateStreamCallable = { - assertEquals(step++, 0) + assertOrder(order, 0) templateDbStream } val listener = object : InterceptorOpenHelperListener { override fun onWritableDatabaseCalled() { - assertEquals(step++, 1) + assertOrder(order, 1) } } @@ -98,34 +99,34 @@ class CorruptedDatabaseTest { InterceptorOpenHelperFactory(openHelperFactory(), listener), // TEST_DB // ).useFinally({ db -> - assertEquals(step++, 2) + assertOrder(order, 2) db.compileStatement("SELECT count(*) FROM `sqlite_master` WHERE `name` = 'CLOUD_ENTITY'").use { statement -> require(statement.simpleQueryForLong() == 1L) } }, finallyBlock = CryptomatorDatabase::close) - assertEquals(step++, 3) + assertOrder(order, 3) } @Test fun testOpenDatabaseWithRecovery() { - var step = 0 + val order = Order() val templateStreamCallable = { - assertEquals(step++, 0) + assertOrder(order, 0) throw IOException() } val listener = object : InterceptorOpenHelperListener { override fun onWritableDatabaseCalled() { - assertEquals(step++, 1) + assertOrder(order, 1) } override fun onWritableDatabaseThrew(exc: Exception): Exception { - assertEquals(step++, 3) + assertOrder(order, 3) assertThat(exc, instanceOf(UnsupportedOperationException::class.java)) return WrappedException(exc) } } val openHelperFactory = openHelperFactory { - assertEquals(step++, 2) + assertOrder(order, 2) } createVersion0Database(context, TEST_DB) @@ -142,7 +143,7 @@ class CorruptedDatabaseTest { }.also { assertThat(it.cause, instanceOf(UnsupportedOperationException::class.java)) } - assertEquals(step++, 4) + assertOrder(order, 4) } } diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java index 3ace6d7dc..c85e73d73 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java +++ b/data/src/androidTest/java/org/cryptomator/data/db/CryptomatorAssert.java @@ -5,7 +5,12 @@ import com.google.android.gms.common.util.Strings; import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.regex.Pattern; +import java.util.stream.IntStream; import static org.junit.Assert.fail; @@ -57,4 +62,44 @@ public static void assertIsUUID(String message, String actual) { actual != null ? '"' + actual + '"' : ""); fail(failMessage); } + + public static void assertOrder(Order order, int firstStep, int... moreSteps) { + order.assertOrder(firstStep, moreSteps); + } + + public static class Order { + + private final Object lock = new Object(); + private final Map> recognized = new HashMap<>(); + + private int currentStep = 0; + + public void assertOrder(int firstStep, int... moreSteps) { + List steps = IntStream.concat( // + IntStream.of(firstStep), // + Arrays.stream(moreSteps) // + ).boxed().toList(); + + assertOrder(steps); + } + + private void assertOrder(List steps) { + synchronized (lock) { + for (Integer step : steps) { + List existing = recognized.get(step); + if (existing != null) { + if (!existing.equals(steps)) { + fail("Step has been assigned twice!"); + } + } else { + recognized.put(step, steps); + } + } + if (!steps.contains(currentStep)) { + fail("Expected step was any of %s; current step is <%d>".formatted(steps, currentStep)); + } + currentStep++; + } + } + } } \ No newline at end of file From 4951ebb410f65ea510b57d38a4262df1780a935b Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Mon, 21 Oct 2024 00:21:10 +0200 Subject: [PATCH 120/120] Fixed ordered tests in "CorruptedDatabaseTest" Fixed "testOpenVersion0DatabaseVerifyStreamAccessed": "CorruptedDatabaseTest.kt:103" calls "RoomDatabase.compileStatement", which contains two calls to "writableDatabase" that were not previously accounted for. Fixed "testOpenDatabaseWithRecovery": The assertion in "CorruptedDatabaseTest.kt:129" is called by "PatchedCallback.onCreate" in "DatabaseOpenHelperFactory.kt:61", which is called by "FrameworkSQLiteOpenHelper.OpenHelper.innerGetDatabase". "PatchedCallback.onCreate" then calls "DatabaseCallback.onCreate", which then throws the "UnsupportedOperationException" ("DatabaseModule.kt:137") expected by the assertion in "CorruptedDatabaseTest.kt:123". However, it was not previously taken into account that "FrameworkSQLiteOpenHelper.OpenHelper.innerGetDatabase" discards the first thrown exception and tries again, which results in a second call to "PatchedCallback.onCreate" and therefore a second call to the assertion in "CorruptedDatabaseTest.kt:129" before the assertion in "CorruptedDatabaseTest.kt:123" is reached. --- .../org/cryptomator/data/db/CorruptedDatabaseTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt index d499c38de..49c50cd05 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/CorruptedDatabaseTest.kt @@ -87,7 +87,7 @@ class CorruptedDatabaseTest { } val listener = object : InterceptorOpenHelperListener { override fun onWritableDatabaseCalled() { - assertOrder(order, 1) + assertOrder(order, 1, 3, 4) } } @@ -104,7 +104,7 @@ class CorruptedDatabaseTest { require(statement.simpleQueryForLong() == 1L) } }, finallyBlock = CryptomatorDatabase::close) - assertOrder(order, 3) + assertOrder(order, 5) } @Test @@ -120,13 +120,13 @@ class CorruptedDatabaseTest { } override fun onWritableDatabaseThrew(exc: Exception): Exception { - assertOrder(order, 3) + assertOrder(order, 4) assertThat(exc, instanceOf(UnsupportedOperationException::class.java)) return WrappedException(exc) } } val openHelperFactory = openHelperFactory { - assertOrder(order, 2) + assertOrder(order, 2, 3) } createVersion0Database(context, TEST_DB) @@ -143,7 +143,7 @@ class CorruptedDatabaseTest { }.also { assertThat(it.cause, instanceOf(UnsupportedOperationException::class.java)) } - assertOrder(order, 4) + assertOrder(order, 5) } }