From 538fd8a33ce2b413746cfe76724915ed7b90a307 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sun, 25 Sep 2022 19:16:12 +0800 Subject: [PATCH] Fix ExplorerDatabase migration The OpenMode ID was shifted during FTPClient support implementation, forgotten to update ExplorerDatabase, causing crashes for some --- .../database/ExplorerDatabase.java | 299 ---------------- .../filemanager/database/ExplorerDatabase.kt | 321 +++++++++++++++++ .../filemanager/database/UtilitiesDatabase.kt | 2 +- .../ExplorerDatabaseMigrationTest.java | 225 ------------ .../database/ExplorerDatabaseMigrationTest.kt | 330 ++++++++++++++++++ .../9.json | 136 ++++++++ 6 files changed, 788 insertions(+), 525 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.java create mode 100644 app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt delete mode 100644 app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.java create mode 100644 app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt create mode 100644 app/src/test/resources/schemas/com.amaze.filemanager.database.ExplorerDatabase/9.json diff --git a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.java b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.java deleted file mode 100644 index 7fc67d016a..0000000000 --- a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.database; - -import static com.amaze.filemanager.database.ExplorerDatabase.DATABASE_VERSION; - -import com.amaze.filemanager.database.daos.CloudEntryDao; -import com.amaze.filemanager.database.daos.EncryptedEntryDao; -import com.amaze.filemanager.database.daos.SortDao; -import com.amaze.filemanager.database.daos.TabDao; -import com.amaze.filemanager.database.models.explorer.CloudEntry; -import com.amaze.filemanager.database.models.explorer.EncryptedEntry; -import com.amaze.filemanager.database.models.explorer.Sort; -import com.amaze.filemanager.database.models.explorer.Tab; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.arch.core.util.Function; -import androidx.room.Database; -import androidx.room.Room; -import androidx.room.RoomDatabase; -import androidx.room.migration.Migration; -import androidx.sqlite.db.SupportSQLiteDatabase; - -/** - * Repository for {@link Tab}, {@link Sort}, {@link EncryptedEntry}, {@link CloudEntry} in - * explorer.db in Amaze. - * - * @see RoomDatabase - */ -@Database( - entities = {Tab.class, Sort.class, EncryptedEntry.class, CloudEntry.class}, - version = DATABASE_VERSION) -public abstract class ExplorerDatabase extends RoomDatabase { - - private static final String DATABASE_NAME = "explorer.db"; - protected static final int DATABASE_VERSION = 8; - - public static final String TABLE_TAB = "tab"; - public static final String TABLE_CLOUD_PERSIST = "cloud"; - public static final String TABLE_ENCRYPTED = "encrypted"; - public static final String TABLE_SORT = "sort"; - - public static final String COLUMN_TAB_NO = "tab_no"; - public static final String COLUMN_PATH = "path"; - public static final String COLUMN_HOME = "home"; - - public static final String COLUMN_ENCRYPTED_ID = "_id"; - public static final String COLUMN_ENCRYPTED_PATH = "path"; - public static final String COLUMN_ENCRYPTED_PASSWORD = "password"; - - public static final String COLUMN_CLOUD_ID = "_id"; - public static final String COLUMN_CLOUD_SERVICE = "service"; - public static final String COLUMN_CLOUD_PERSIST = "persist"; - - public static final String COLUMN_SORT_PATH = "path"; - public static final String COLUMN_SORT_TYPE = "type"; - - @VisibleForTesting - public static Function> overrideDatabaseBuilder = null; - - private static final String TEMP_TABLE_PREFIX = "temp_"; - - // 1->2: add encrypted table (66f08f34) - static final Migration MIGRATION_1_2 = - new Migration(1, 2) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - String CREATE_TABLE_ENCRYPTED = - "CREATE TABLE " - + TABLE_ENCRYPTED - + "(" - + COLUMN_ENCRYPTED_ID - + " INTEGER PRIMARY KEY," - + COLUMN_ENCRYPTED_PATH - + " TEXT," - + COLUMN_ENCRYPTED_PASSWORD - + " TEXT" - + ")"; - database.execSQL(CREATE_TABLE_ENCRYPTED); - } - }; - - // 2->3: add cloud table (8a5ced1b) - static final Migration MIGRATION_2_3 = - new Migration(2, 3) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - String CREATE_TABLE_CLOUD = - "CREATE TABLE " - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + " INTEGER PRIMARY KEY," - + COLUMN_CLOUD_SERVICE - + " INTEGER," - + COLUMN_CLOUD_PERSIST - + " TEXT" - + ")"; - database.execSQL(CREATE_TABLE_CLOUD); - } - }; - - // 3->4: same as 2->3 (765140f6) - static final Migration MIGRATION_3_4 = - new Migration(3, 4) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) {} - }; - - // 4->5: same as 3->4, same as 2->3 (37357436) - static final Migration MIGRATION_4_5 = - new Migration(4, 5) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) {} - }; - - // 5->6: add sort table (fe7c0aba) - static final Migration MIGRATION_5_6 = - new Migration(5, 6) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL( - "CREATE TABLE " - + TABLE_SORT - + "(" - + COLUMN_SORT_PATH - + " TEXT PRIMARY KEY," - + COLUMN_SORT_TYPE - + " INTEGER" - + ")"); - } - }; - - static final Migration MIGRATION_6_7 = - new Migration(6, DATABASE_VERSION) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL( - "CREATE TABLE " - + TEMP_TABLE_PREFIX - + TABLE_TAB - + "(" - + COLUMN_TAB_NO - + " INTEGER PRIMARY KEY NOT NULL, " - + COLUMN_PATH - + " TEXT, " - + COLUMN_HOME - + " TEXT)"); - database.execSQL( - "INSERT INTO " - + TEMP_TABLE_PREFIX - + TABLE_TAB - + "(" - + COLUMN_TAB_NO - + "," - + COLUMN_PATH - + "," - + COLUMN_HOME - + ")" - + " SELECT " - + COLUMN_TAB_NO - + "," - + COLUMN_PATH - + "," - + COLUMN_HOME - + " FROM " - + TABLE_TAB); - database.execSQL("DROP TABLE " + TABLE_TAB); - database.execSQL( - "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_TAB + " RENAME TO " + TABLE_TAB); - - database.execSQL( - "CREATE TABLE " - + TEMP_TABLE_PREFIX - + TABLE_SORT - + "(" - + COLUMN_SORT_PATH - + " TEXT PRIMARY KEY NOT NULL, " - + COLUMN_SORT_TYPE - + " INTEGER NOT NULL)"); - database.execSQL( - "INSERT INTO " + TEMP_TABLE_PREFIX + TABLE_SORT + " SELECT * FROM " + TABLE_SORT); - database.execSQL("DROP TABLE " + TABLE_SORT); - database.execSQL( - "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_SORT + " RENAME TO " + TABLE_SORT); - - database.execSQL( - "CREATE TABLE " - + TEMP_TABLE_PREFIX - + TABLE_ENCRYPTED - + "(" - + COLUMN_ENCRYPTED_ID - + " INTEGER PRIMARY KEY NOT NULL," - + COLUMN_ENCRYPTED_PATH - + " TEXT," - + COLUMN_ENCRYPTED_PASSWORD - + " TEXT)"); - database.execSQL( - "INSERT INTO " - + TEMP_TABLE_PREFIX - + TABLE_ENCRYPTED - + " SELECT * FROM " - + TABLE_ENCRYPTED); - database.execSQL("DROP TABLE " + TABLE_ENCRYPTED); - database.execSQL( - "ALTER TABLE " - + TEMP_TABLE_PREFIX - + TABLE_ENCRYPTED - + " RENAME TO " - + TABLE_ENCRYPTED); - - database.execSQL( - "CREATE TABLE " - + TEMP_TABLE_PREFIX - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + " INTEGER PRIMARY KEY NOT NULL," - + COLUMN_CLOUD_SERVICE - + " INTEGER," - + COLUMN_CLOUD_PERSIST - + " TEXT)"); - database.execSQL( - "INSERT INTO " - + TEMP_TABLE_PREFIX - + TABLE_CLOUD_PERSIST - + " SELECT * FROM " - + TABLE_CLOUD_PERSIST); - database.execSQL("DROP TABLE " + TABLE_CLOUD_PERSIST); - database.execSQL( - "ALTER TABLE " - + TEMP_TABLE_PREFIX - + TABLE_CLOUD_PERSIST - + " RENAME TO " - + TABLE_CLOUD_PERSIST); - } - }; - - static final Migration MIGRATION_7_8 = - new Migration(7, DATABASE_VERSION) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL( - "UPDATE " - + TABLE_CLOUD_PERSIST - + " SET " - + COLUMN_CLOUD_SERVICE - + " = " - + COLUMN_CLOUD_SERVICE - + "+1"); - } - }; - - protected abstract TabDao tabDao(); - - protected abstract SortDao sortDao(); - - protected abstract EncryptedEntryDao encryptedEntryDao(); - - protected abstract CloudEntryDao cloudEntryDao(); - - public static synchronized ExplorerDatabase initialize(@NonNull Context context) { - Builder builder = - (overrideDatabaseBuilder == null) - ? Room.databaseBuilder(context, ExplorerDatabase.class, DATABASE_NAME) - : overrideDatabaseBuilder.apply(context); - return builder - .addMigrations(MIGRATION_1_2) - .addMigrations(MIGRATION_2_3) - .addMigrations(MIGRATION_3_4) - .addMigrations(MIGRATION_4_5) - .addMigrations(MIGRATION_5_6) - .addMigrations(MIGRATION_6_7) - .addMigrations(MIGRATION_7_8) - .allowMainThreadQueries() - .build(); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt new file mode 100644 index 0000000000..a2a582baf9 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.database + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.amaze.filemanager.database.daos.CloudEntryDao +import com.amaze.filemanager.database.daos.EncryptedEntryDao +import com.amaze.filemanager.database.daos.SortDao +import com.amaze.filemanager.database.daos.TabDao +import com.amaze.filemanager.database.models.explorer.CloudEntry +import com.amaze.filemanager.database.models.explorer.EncryptedEntry +import com.amaze.filemanager.database.models.explorer.Sort +import com.amaze.filemanager.database.models.explorer.Tab + +/** + * Repository for [Tab], [Sort], [EncryptedEntry], [CloudEntry] in + * explorer.db in Amaze. + * + * @see RoomDatabase + */ +@Database( + entities = [Tab::class, Sort::class, EncryptedEntry::class, CloudEntry::class], + version = ExplorerDatabase.DATABASE_VERSION +) +@Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod") +abstract class ExplorerDatabase : RoomDatabase() { + + /** + * Returns DAO for [Tab] objects. + */ + abstract fun tabDao(): TabDao + + /** + * Returns DAO for [Sort] objects. + */ + abstract fun sortDao(): SortDao + + /** + * Returns DAO for [EncryptedEntry] objects. + */ + abstract fun encryptedEntryDao(): EncryptedEntryDao + + /** + * Returns DAO for [CloudEntry] objects. + */ + abstract fun cloudEntryDao(): CloudEntryDao + + companion object { + private const val DATABASE_NAME = "explorer.db" + const val DATABASE_VERSION = 9 + const val TABLE_TAB = "tab" + const val TABLE_CLOUD_PERSIST = "cloud" + const val TABLE_ENCRYPTED = "encrypted" + const val TABLE_SORT = "sort" + const val COLUMN_TAB_NO = "tab_no" + const val COLUMN_PATH = "path" + const val COLUMN_HOME = "home" + const val COLUMN_ENCRYPTED_ID = "_id" + const val COLUMN_ENCRYPTED_PATH = "path" + const val COLUMN_ENCRYPTED_PASSWORD = "password" + const val COLUMN_CLOUD_ID = "_id" + const val COLUMN_CLOUD_SERVICE = "service" + const val COLUMN_CLOUD_PERSIST = "persist" + const val COLUMN_SORT_PATH = "path" + const val COLUMN_SORT_TYPE = "type" + + @VisibleForTesting + var overrideDatabaseBuilder: ((Context) -> Builder)? = null + + private const val TEMP_TABLE_PREFIX = "temp_" + + // 1->2: add encrypted table (66f08f34) + internal val MIGRATION_1_2: Migration = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + val CREATE_TABLE_ENCRYPTED = ( + "CREATE TABLE " + + TABLE_ENCRYPTED + + "(" + + COLUMN_ENCRYPTED_ID + + " INTEGER PRIMARY KEY," + + COLUMN_ENCRYPTED_PATH + + " TEXT," + + COLUMN_ENCRYPTED_PASSWORD + + " TEXT" + + ")" + ) + database.execSQL(CREATE_TABLE_ENCRYPTED) + } + } + + // 2->3: add cloud table (8a5ced1b) + internal val MIGRATION_2_3: Migration = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + val CREATE_TABLE_CLOUD = ( + "CREATE TABLE " + + TABLE_CLOUD_PERSIST + + "(" + + COLUMN_CLOUD_ID + + " INTEGER PRIMARY KEY," + + COLUMN_CLOUD_SERVICE + + " INTEGER," + + COLUMN_CLOUD_PERSIST + + " TEXT" + + ")" + ) + database.execSQL(CREATE_TABLE_CLOUD) + } + } + + // 3->4: same as 2->3 (765140f6) + internal val MIGRATION_3_4: Migration = object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) = Unit + } + + // 4->5: same as 3->4, same as 2->3 (37357436) + internal val MIGRATION_4_5: Migration = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) = Unit + } + + // 5->6: add sort table (fe7c0aba) + internal val MIGRATION_5_6: Migration = object : Migration(5, 6) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE " + + TABLE_SORT + + "(" + + COLUMN_SORT_PATH + + " TEXT PRIMARY KEY," + + COLUMN_SORT_TYPE + + " INTEGER" + + ")" + ) + } + } + internal val MIGRATION_6_7: Migration = object : Migration(6, DATABASE_VERSION) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_TAB + + "(" + + COLUMN_TAB_NO + + " INTEGER PRIMARY KEY NOT NULL, " + + COLUMN_PATH + + " TEXT, " + + COLUMN_HOME + + " TEXT)" + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_TAB + + "(" + + COLUMN_TAB_NO + + "," + + COLUMN_PATH + + "," + + COLUMN_HOME + + ")" + + " SELECT " + + COLUMN_TAB_NO + + "," + + COLUMN_PATH + + "," + + COLUMN_HOME + + " FROM " + + TABLE_TAB + ) + database.execSQL("DROP TABLE " + TABLE_TAB) + database.execSQL( + "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_TAB + " RENAME TO " + TABLE_TAB + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_SORT + + "(" + + COLUMN_SORT_PATH + + " TEXT PRIMARY KEY NOT NULL, " + + COLUMN_SORT_TYPE + + " INTEGER NOT NULL)" + ) + database.execSQL( + "INSERT INTO " + TEMP_TABLE_PREFIX + TABLE_SORT + " SELECT * FROM " + TABLE_SORT + ) + database.execSQL("DROP TABLE " + TABLE_SORT) + database.execSQL( + "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_SORT + " RENAME TO " + TABLE_SORT + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + "(" + + COLUMN_ENCRYPTED_ID + + " INTEGER PRIMARY KEY NOT NULL," + + COLUMN_ENCRYPTED_PATH + + " TEXT," + + COLUMN_ENCRYPTED_PASSWORD + + " TEXT)" + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + " SELECT * FROM " + + TABLE_ENCRYPTED + ) + database.execSQL("DROP TABLE " + TABLE_ENCRYPTED) + database.execSQL( + "ALTER TABLE " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + " RENAME TO " + + TABLE_ENCRYPTED + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + "(" + + COLUMN_CLOUD_ID + + " INTEGER PRIMARY KEY NOT NULL," + + COLUMN_CLOUD_SERVICE + + " INTEGER," + + COLUMN_CLOUD_PERSIST + + " TEXT)" + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + " SELECT * FROM " + + TABLE_CLOUD_PERSIST + ) + database.execSQL("DROP TABLE " + TABLE_CLOUD_PERSIST) + database.execSQL( + "ALTER TABLE " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + " RENAME TO " + + TABLE_CLOUD_PERSIST + ) + } + } + internal val MIGRATION_7_8: Migration = object : Migration(7, 8) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "+1" + ) + } + } + internal val MIGRATION_8_9: Migration = object : Migration(8, DATABASE_VERSION) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "+1" + ) + } + } + + /** + * Initialize the database. Optionally, may provide a custom way to create the database + * with supplied [Context]. + */ + @JvmStatic + fun initialize(context: Context): ExplorerDatabase { + val builder = overrideDatabaseBuilder?.invoke(context) ?: Room.databaseBuilder( + context, + ExplorerDatabase::class.java, + DATABASE_NAME + ) + return builder + .addMigrations(MIGRATION_1_2) + .addMigrations(MIGRATION_2_3) + .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) + .addMigrations(MIGRATION_5_6) + .addMigrations(MIGRATION_6_7) + .addMigrations(MIGRATION_7_8) + .addMigrations(MIGRATION_8_9) + .allowMainThreadQueries() + .build() + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt index 5359766026..eb059f5686 100644 --- a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt +++ b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt @@ -127,7 +127,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { const val COLUMN_PRIVATE_KEY = "ssh_key" @VisibleForTesting - var overrideDatabaseBuilder: ((Context) -> RoomDatabase.Builder)? = null + var overrideDatabaseBuilder: ((Context) -> Builder)? = null private const val TEMP_TABLE_PREFIX = "temp_" private const val queryHistory = ( diff --git a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.java b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.java deleted file mode 100644 index 0e7ff7f209..0000000000 --- a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.database; - -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.KITKAT; -import static android.os.Build.VERSION_CODES.P; -import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_CLOUD_ID; -import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_CLOUD_PERSIST; -import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_CLOUD_SERVICE; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_1_2; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_2_3; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_3_4; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_4_5; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_5_6; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_6_7; -import static com.amaze.filemanager.database.ExplorerDatabase.MIGRATION_7_8; -import static com.amaze.filemanager.database.ExplorerDatabase.TABLE_CLOUD_PERSIST; -import static org.junit.Assert.assertEquals; - -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; - -import com.amaze.filemanager.database.models.explorer.CloudEntry; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.shadows.ShadowMultiDex; -import com.amaze.filemanager.test.ShadowPasswordUtil; - -import androidx.room.Room; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import io.reactivex.schedulers.Schedulers; - -@RunWith(AndroidJUnit4.class) -@Config( - shadows = {ShadowMultiDex.class, ShadowPasswordUtil.class}, - sdk = {JELLY_BEAN, KITKAT, P}) -public class ExplorerDatabaseMigrationTest { - - private static final String TEST_DB = "explorer-test"; - - @Rule - public final MigrationTestHelper helper = - new MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - ExplorerDatabase.class.getCanonicalName(), - new FrameworkSQLiteOpenHelperFactory()); - - @Test - public void migrateAll() throws IOException { - SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1); - db.close(); - - ExplorerDatabase explorerDatabase = - Room.databaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - ExplorerDatabase.class, - TEST_DB) - .addMigrations( - MIGRATION_1_2, - MIGRATION_2_3, - MIGRATION_3_4, - MIGRATION_4_5, - MIGRATION_5_6, - MIGRATION_6_7) - .build(); - explorerDatabase.getOpenHelper().getWritableDatabase(); - explorerDatabase.close(); - } - - @Test - public void migrateFromV5() throws IOException { - SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 5); - db.close(); - - ExplorerDatabase explorerDatabase = - Room.databaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - ExplorerDatabase.class, - TEST_DB) - .addMigrations(MIGRATION_5_6, MIGRATION_6_7) - .build(); - explorerDatabase.getOpenHelper().getWritableDatabase(); - explorerDatabase.close(); - } - - @Test - public void migrateFromV6() throws IOException { - SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6); - db.close(); - - ExplorerDatabase explorerDatabase = - Room.databaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - ExplorerDatabase.class, - TEST_DB) - .addMigrations(MIGRATION_6_7) - .build(); - explorerDatabase.getOpenHelper().getWritableDatabase(); - explorerDatabase.close(); - } - - @Test - public void migrateFromV7() throws IOException { - SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 7); - db.execSQL( - "INSERT INTO " - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + "," - + COLUMN_CLOUD_SERVICE - + "," - + COLUMN_CLOUD_PERSIST - + ") VALUES (1," - + (OpenMode.GDRIVE.ordinal() - 1) - + ",'abcd')"); - db.execSQL( - "INSERT INTO " - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + "," - + COLUMN_CLOUD_SERVICE - + "," - + COLUMN_CLOUD_PERSIST - + ") VALUES (2," - + (OpenMode.DROPBOX.ordinal() - 1) - + ",'efgh')"); - db.execSQL( - "INSERT INTO " - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + "," - + COLUMN_CLOUD_SERVICE - + "," - + COLUMN_CLOUD_PERSIST - + ") VALUES (3," - + (OpenMode.BOX.ordinal() - 1) - + ",'ijkl')"); - db.execSQL( - "INSERT INTO " - + TABLE_CLOUD_PERSIST - + "(" - + COLUMN_CLOUD_ID - + "," - + COLUMN_CLOUD_SERVICE - + "," - + COLUMN_CLOUD_PERSIST - + ") VALUES (4," - + (OpenMode.ONEDRIVE.ordinal() - 1) - + ",'mnop')"); - db.close(); - - ExplorerDatabase explorerDatabase = - Room.databaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - ExplorerDatabase.class, - TEST_DB) - .addMigrations(MIGRATION_7_8) - .allowMainThreadQueries() - .build(); - explorerDatabase.getOpenHelper().getWritableDatabase(); - CloudEntry verify = - explorerDatabase - .cloudEntryDao() - .findByServiceType(OpenMode.GDRIVE.ordinal()) - .subscribeOn(Schedulers.trampoline()) - .blockingGet(); - assertEquals(1, verify.getId()); - assertEquals("abcd", verify.getPersistData().toString()); - verify = - explorerDatabase - .cloudEntryDao() - .findByServiceType(OpenMode.BOX.ordinal()) - .subscribeOn(Schedulers.trampoline()) - .blockingGet(); - assertEquals(3, verify.getId()); - assertEquals("ijkl", verify.getPersistData().toString()); - verify = - explorerDatabase - .cloudEntryDao() - .findByServiceType(OpenMode.DROPBOX.ordinal()) - .subscribeOn(Schedulers.trampoline()) - .blockingGet(); - assertEquals(2, verify.getId()); - assertEquals("efgh", verify.getPersistData().toString()); - verify = - explorerDatabase - .cloudEntryDao() - .findByServiceType(OpenMode.ONEDRIVE.ordinal()) - .subscribeOn(Schedulers.trampoline()) - .blockingGet(); - assertEquals(4, verify.getId()); - assertEquals("mnop", verify.getPersistData().toString()); - - explorerDatabase.close(); - } -} diff --git a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt new file mode 100644 index 0000000000..84a76be94d --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.database + +import android.os.Build.VERSION_CODES.JELLY_BEAN +import android.os.Build.VERSION_CODES.KITKAT +import android.os.Build.VERSION_CODES.P +import androidx.room.Room +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.shadows.ShadowMultiDex +import com.amaze.filemanager.test.ShadowPasswordUtil +import io.reactivex.schedulers.Schedulers +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import java.io.IOException + +/** + * Tests for [ExplorerDatabase] migrations. + */ +@RunWith(AndroidJUnit4::class) +@Config( + shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class], + sdk = [JELLY_BEAN, KITKAT, P] +) +@Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod") +class ExplorerDatabaseMigrationTest { + + @Rule + @JvmField + val helper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + ExplorerDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() + ) + + /** + * Sanity check for all migrations. + */ + @Test + @Throws(IOException::class) + fun migrateAll() { + val db = helper.createDatabase(TEST_DB, 1) + db.close() + val explorerDatabase = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ExplorerDatabase::class.java, + TEST_DB + ) + .addMigrations( + ExplorerDatabase.MIGRATION_1_2, + ExplorerDatabase.MIGRATION_2_3, + ExplorerDatabase.MIGRATION_3_4, + ExplorerDatabase.MIGRATION_4_5, + ExplorerDatabase.MIGRATION_5_6, + ExplorerDatabase.MIGRATION_6_7, + ExplorerDatabase.MIGRATION_7_8, + ExplorerDatabase.MIGRATION_8_9 + ) + .build() + explorerDatabase.openHelper.writableDatabase + explorerDatabase.close() + } + + /** + * Test migrate from v5 to v6 - add sort table. + */ + @Test + @Throws(IOException::class) + fun migrateFromV5() { + val db = helper.createDatabase(TEST_DB, 5) + db.close() + val explorerDatabase = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ExplorerDatabase::class.java, + TEST_DB + ) + .addMigrations(ExplorerDatabase.MIGRATION_5_6, ExplorerDatabase.MIGRATION_6_7) + .build() + explorerDatabase.openHelper.writableDatabase + explorerDatabase.close() + } + + /** + * Test migrate from v6 to v7 - fix primary keys + */ + @Test + @Throws(IOException::class) + fun migrateFromV6() { + val db = helper.createDatabase(TEST_DB, 6) + db.close() + val explorerDatabase = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ExplorerDatabase::class.java, + TEST_DB + ) + .addMigrations(ExplorerDatabase.MIGRATION_6_7) + .build() + explorerDatabase.openHelper.writableDatabase + explorerDatabase.close() + } + + /** + * Test migrate from v7 to v8, after shifting OpenMode by 1. + */ + @Test + @Throws(IOException::class) + fun migrateFromV7() { + val db = helper.createDatabase(TEST_DB, 7) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (1," + + (OpenMode.GDRIVE.ordinal - 2) + + ",'abcd')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (2," + + (OpenMode.DROPBOX.ordinal - 2) + + ",'efgh')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (3," + + (OpenMode.BOX.ordinal - 2) + + ",'ijkl')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (4," + + (OpenMode.ONEDRIVE.ordinal - 2) + + ",'mnop')" + ) + db.close() + val explorerDatabase = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ExplorerDatabase::class.java, + TEST_DB + ) + .addMigrations(ExplorerDatabase.MIGRATION_7_8) + .addMigrations(ExplorerDatabase.MIGRATION_8_9) + .allowMainThreadQueries() + .build() + explorerDatabase.openHelper.writableDatabase + var verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.GDRIVE.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(1, verify.id.toLong()) + Assert.assertEquals("abcd", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.BOX.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(3, verify.id.toLong()) + Assert.assertEquals("ijkl", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.DROPBOX.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(2, verify.id.toLong()) + Assert.assertEquals("efgh", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.ONEDRIVE.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(4, verify.id.toLong()) + Assert.assertEquals("mnop", verify.persistData.toString()) + explorerDatabase.close() + } + + /** + * Test migrate from v8 to v9, after shifting OpenMode by 1 again. + */ + @Test + @Throws(IOException::class) + fun migrateFromV8() { + val db = helper.createDatabase(TEST_DB, 8) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (1," + + (OpenMode.GDRIVE.ordinal - 1) + + ",'abcd')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (2," + + (OpenMode.DROPBOX.ordinal - 1) + + ",'efgh')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (3," + + (OpenMode.BOX.ordinal - 1) + + ",'ijkl')" + ) + db.execSQL( + "INSERT INTO " + + ExplorerDatabase.TABLE_CLOUD_PERSIST + + "(" + + ExplorerDatabase.COLUMN_CLOUD_ID + + "," + + ExplorerDatabase.COLUMN_CLOUD_SERVICE + + "," + + ExplorerDatabase.COLUMN_CLOUD_PERSIST + + ") VALUES (4," + + (OpenMode.ONEDRIVE.ordinal - 1) + + ",'mnop')" + ) + db.close() + val explorerDatabase = Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ExplorerDatabase::class.java, + TEST_DB + ) + .addMigrations(ExplorerDatabase.MIGRATION_8_9) + .allowMainThreadQueries() + .build() + explorerDatabase.openHelper.writableDatabase + var verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.GDRIVE.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(1, verify.id.toLong()) + Assert.assertEquals("abcd", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.BOX.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(3, verify.id.toLong()) + Assert.assertEquals("ijkl", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.DROPBOX.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(2, verify.id.toLong()) + Assert.assertEquals("efgh", verify.persistData.toString()) + verify = explorerDatabase + .cloudEntryDao() + .findByServiceType(OpenMode.ONEDRIVE.ordinal) + .subscribeOn(Schedulers.trampoline()) + .blockingGet() + Assert.assertEquals(4, verify.id.toLong()) + Assert.assertEquals("mnop", verify.persistData.toString()) + explorerDatabase.close() + } + + companion object { + private const val TEST_DB = "explorer-test" + } +} diff --git a/app/src/test/resources/schemas/com.amaze.filemanager.database.ExplorerDatabase/9.json b/app/src/test/resources/schemas/com.amaze.filemanager.database.ExplorerDatabase/9.json new file mode 100644 index 0000000000..1672897d56 --- /dev/null +++ b/app/src/test/resources/schemas/com.amaze.filemanager.database.ExplorerDatabase/9.json @@ -0,0 +1,136 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "06766b689063c1bcaa4d0df3fd06f5e6", + "entities": [ + { + "tableName": "tab", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tab_no` INTEGER NOT NULL, `path` TEXT, `home` TEXT, PRIMARY KEY(`tab_no`))", + "fields": [ + { + "fieldPath": "tabNumber", + "columnName": "tab_no", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "home", + "columnName": "home", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "tab_no" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sort", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`path` TEXT NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`path`))", + "fields": [ + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "path" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "encrypted", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT, `password` TEXT)", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "cloud", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service` INTEGER, `persist` TEXT)", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceType", + "columnName": "service", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "persistData", + "columnName": "persist", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, '06766b689063c1bcaa4d0df3fd06f5e6')" + ] + } +} \ No newline at end of file