From daf4ce9cb17a69200570f90bc77817d3008678c8 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 17 Jun 2022 16:14:43 +1000 Subject: [PATCH] Drop the legacy upgrade/initialization code Schema 11 was introduced in 2012, and decks that still are <11 are very rare. The desktop dropped support for schema 10 back in early 2020. This also removes the need to modify SCHEMA_VERSION when switching between TESTING_USE_V16_BACKEND. --- .../java/com/ichi2/anki/AnkiDroidApp.java | 17 +- .../java/com/ichi2/anki/CollectionHelper.java | 2 - .../main/java/com/ichi2/anki/Preferences.kt | 2 - .../ichi2/anki/dialogs/DatabaseErrorDialog.kt | 3 +- .../src/main/java/com/ichi2/libanki/Consts.kt | 5 +- .../main/java/com/ichi2/libanki/Storage.java | 284 ++---------------- .../com/ichi2/libanki/backend/DroidBackend.kt | 2 - .../ichi2/libanki/backend/RustDroidBackend.kt | 4 - 8 files changed, 45 insertions(+), 274 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index 052f176b2986..1b1c65b4b879 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -22,7 +22,6 @@ import android.app.Application; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; @@ -44,9 +43,7 @@ import com.ichi2.anki.services.BootService; import com.ichi2.anki.services.NotificationService; import com.ichi2.compat.CompatHelper; -import com.ichi2.libanki.backend.DroidBackend; -import com.ichi2.libanki.backend.RustDroidBackend; -import com.ichi2.libanki.backend.RustDroidV16Backend; +import com.ichi2.libanki.Consts; import com.ichi2.utils.AdaptionUtil; import com.ichi2.utils.ExceptionUtil; import com.ichi2.utils.LanguageUtil; @@ -89,11 +86,9 @@ public class AnkiDroidApp extends Application { /** * Toggles opening the collection using schema 16 via the Rust backend - * and using the V16 versions of the major 'col' classes: models, decks, dconf, conf, tags + * and using the V18 versions of the major 'col' classes: models, decks, dconf, conf, tags * * UNSTABLE: DO NOT USE THIS ON A COLLECTION YOU CARE ABOUT. - * - * Set this and {@link com.ichi2.libanki.Consts#SCHEMA_VERSION} to 16. */ public static boolean TESTING_USE_V16_BACKEND = false; @@ -514,4 +509,12 @@ public static BackendFactory currentBackendFactory() { return new BackendV11Factory(); } } + + public static int activeSchemaVersion() { + if (TESTING_USE_V16_BACKEND) { + return Consts.BACKEND_SCHEMA_VERSION; + } else { + return Consts.LEGACY_SCHEMA_VERSION; + } + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java index df3f5b76099a..51ee882dcf58 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java @@ -43,8 +43,6 @@ import androidx.annotation.VisibleForTesting; import timber.log.Timber; -import static com.ichi2.libanki.Consts.SCHEMA_VERSION; - /** * Singleton which opens, stores, and closes the reference to the Collection. */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.kt index bcce69280025..1a42c748c77d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.kt @@ -61,7 +61,6 @@ import com.ichi2.anki.web.CustomSyncServer.handleSyncServerPreferenceChange import com.ichi2.anki.web.CustomSyncServer.isEnabled import com.ichi2.compat.CompatHelper import com.ichi2.libanki.Collection -import com.ichi2.libanki.Consts import com.ichi2.libanki.Utils import com.ichi2.libanki.backend.exception.BackendNotSupportedException import com.ichi2.libanki.utils.TimeManager @@ -1314,7 +1313,6 @@ class Preferences : AnkiActivity() { setDefaultValue(AnkiDroidApp.TESTING_USE_V16_BACKEND) setOnPreferenceClickListener { AnkiDroidApp.TESTING_USE_V16_BACKEND = true - Consts.SCHEMA_VERSION = 16 (requireActivity() as Preferences).restartWithNewDeckPicker() true } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt index 959e7dffef9b..b9b0ac9f08e4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt @@ -26,7 +26,6 @@ import com.afollestad.materialdialogs.DialogAction import com.afollestad.materialdialogs.MaterialDialog import com.ichi2.anki.* import com.ichi2.async.Connection -import com.ichi2.libanki.Consts import com.ichi2.libanki.utils.TimeManager import com.ichi2.utils.SyncStatus import com.ichi2.utils.UiUtil.makeBold @@ -375,7 +374,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() { } catch (e: Exception) { Timber.w(e, "Failed to get database version, using -1") } - res().getString(R.string.incompatible_database_version_summary, Consts.SCHEMA_VERSION, databaseVersion) + res().getString(R.string.incompatible_database_version_summary, AnkiDroidApp.activeSchemaVersion(), databaseVersion) } else -> requireArguments().getString("dialogMessage") } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Consts.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Consts.kt index 251574eb4435..b04fc18b0bec 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Consts.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Consts.kt @@ -104,8 +104,9 @@ object Consts { const val STARTING_FACTOR = 2500 // deck schema & syncing vars - @JvmField - var SCHEMA_VERSION = 11 + const val LEGACY_SCHEMA_VERSION = 11 + /** Only used by the dialog shown to user */ + const val BACKEND_SCHEMA_VERSION = 18 /** The database schema version that we can downgrade from */ const val SYNC_MAX_BYTES = (2.5 * 1024 * 1024).toInt() diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java index 2cc84af60100..9b63ba32d83a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java @@ -43,18 +43,18 @@ import static com.ichi2.libanki.Consts.DECK_STD; -@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.AvoidReassigningParameters", - "PMD.NPathComplexity","PMD.MethodNamingConventions","PMD.ExcessiveMethodLength","PMD.OneDeclarationPerLine", - "PMD.SwitchStmtsShouldHaveDefault","PMD.EmptyIfStmt","PMD.SimplifyBooleanReturns","PMD.CollapsibleIfStatements"}) +@SuppressWarnings( {"PMD.AvoidThrowingRawExceptionTypes", "PMD.AvoidReassigningParameters", + "PMD.NPathComplexity", "PMD.MethodNamingConventions", "PMD.ExcessiveMethodLength", "PMD.OneDeclarationPerLine", + "PMD.SwitchStmtsShouldHaveDefault", "PMD.EmptyIfStmt", "PMD.SimplifyBooleanReturns", "PMD.CollapsibleIfStatements"}) public class Storage { private static boolean sUseInMemory = false; /** * The collection is locked from being opened via the {@link Storage} class. All collection accesses in the app * should use this class. - * + *

* Opening a collection will then throw {@link SQLiteDatabaseLockedException} - * + *

* A collection which was opened before sIsLocked was set will be usable until it is closed. */ private static boolean sIsLocked = false; @@ -65,7 +65,10 @@ public static Collection Collection(Context context, @NonNull String path) { return Collection(context, path, false, false); } - /** Helper method for when the collection can't be opened */ + + /** + * Helper method for when the collection can't be opened + */ public static int getDatabaseVersion(String path) throws UnknownDatabaseVersionException { try { if (!new File(path).exists()) { @@ -81,9 +84,12 @@ public static int getDatabaseVersion(String path) throws UnknownDatabaseVersionE } } + public static Collection Collection(Context context, @NonNull String path, boolean server, boolean log) { return Collection(context, path, server, log, TimeManager.INSTANCE.getTime()); } + + public static Collection Collection(Context context, @NonNull String path, boolean server, boolean log, @NonNull Time time) { assert (path.endsWith(".anki2") || path.endsWith(".anki21")); if (sIsLocked) { @@ -97,19 +103,12 @@ public static Collection Collection(Context context, @NonNull String path, boole try { // initialize - int ver; if (create) { - ver = _createDB(db, time, backend); - } else { - ver = _upgradeSchema(db, time); + _createDB(db, time, backend); } // add db to col and do any remaining upgrades Collection col = backend.createCollection(context, db, path, server, log); - if (ver < Consts.SCHEMA_VERSION) { - _upgrade(col, ver); - } else if (ver > Consts.SCHEMA_VERSION) { - throw new RuntimeException("This file requires a newer version of Anki."); - } else if (create) { + if (create) { addNoteTypes(col, backend); col.onCreate(); col.save(); @@ -122,255 +121,29 @@ public static Collection Collection(Context context, @NonNull String path, boole } } - /** Add note types when creating database */ + + /** + * Add note types when creating database + */ private static void addNoteTypes(Collection col, DroidBackend backend) { if (backend.databaseCreationInitializesData()) { Timber.i("skipping adding note types - already exist"); return; } // add in reverse order so basic is default - for (int i = StdModels.STD_MODELS.length-1; i>=0; i--) { + for (int i = StdModels.STD_MODELS.length - 1; i >= 0; i--) { StdModels.STD_MODELS[i].add(col); } } - private static int _upgradeSchema(DB db, @NonNull Time time) { - int ver = db.queryScalar("SELECT ver FROM col"); - if (ver == Consts.SCHEMA_VERSION) { - return ver; - } - // add odid to cards, edue->odue - if (db.queryScalar("SELECT ver FROM col") == 1) { - db.execute("ALTER TABLE cards RENAME TO cards2"); - _addSchema(db, false, time); - db.execute("insert into cards select id, nid, did, ord, mod, usn, type, queue, due, ivl, factor, reps, lapses, left, edue, 0, flags, data from cards2"); - db.execute("DROP TABLE cards2"); - db.execute("UPDATE col SET ver = 2"); - _updateIndices(db); - } - // remove did from notes - if (db.queryScalar("SELECT ver FROM col") == 2) { - db.execute("ALTER TABLE notes RENAME TO notes2"); - _addSchema(db, true, time); - db.execute("insert into notes select id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data from notes2"); - db.execute("DROP TABLE notes2"); - db.execute("UPDATE col SET ver = 3"); - _updateIndices(db); - } - return ver; - } - - - private static void _upgrade(Collection col, int ver) { - try { - if (ver < 3) { - // new deck properties - for (Deck d : col.getDecks().all()) { - d.put("dyn", DECK_STD); - d.put("collapsed", false); - col.getDecks().save(d); - } - } - if (ver < 4) { - col.modSchemaNoCheck(); - List models = col.getModels().all(); - ArrayList clozes = new ArrayList<>(models.size()); - for (Model m : models) { - if (!m.getJSONArray("tmpls").getJSONObject(0).getString("qfmt").contains("{{cloze:")) { - m.put("type", Consts.MODEL_STD); - } else { - clozes.add(m); - } - } - for (Model m : clozes) { - try { - _upgradeClozeModel(col, m); - } catch (ConfirmModSchemaException e) { - // Will never be reached as we already set modSchemaNoCheck() - throw new RuntimeException(e); - } - } - col.getDb().execute("UPDATE col SET ver = 4"); - } - if (ver < 5) { - col.getDb().execute("UPDATE cards SET odue = 0 WHERE queue = 2"); - col.getDb().execute("UPDATE col SET ver = 5"); - } - if (ver < 6) { - col.modSchemaNoCheck(); - for (Model m : col.getModels().all()) { - m.put("css", new JSONObject(Models.DEFAULT_MODEL).getString("css")); - JSONArray ar = m.getJSONArray("tmpls"); - for (JSONObject t: ar.jsonObjectIterable()) { - if (!t.has("css")) { - continue; - } - m.put("css", - m.getString("css") + "\n" - + t.getString("css").replace(".card ", ".card" + t.getInt("ord") + 1)); - t.remove("css"); - } - col.getModels().save(m); - } - col.getDb().execute("UPDATE col SET ver = 6"); - } - if (ver < 7) { - col.modSchemaNoCheck(); - col.getDb().execute("UPDATE cards SET odue = 0 WHERE (type = " + Consts.CARD_TYPE_LRN + " OR queue = 2) AND NOT odid"); - col.getDb().execute("UPDATE col SET ver = 7"); - } - if (ver < 8) { - col.modSchemaNoCheck(); - col.getDb().execute("UPDATE cards SET due = due / 1000 WHERE due > 4294967296"); - col.getDb().execute("UPDATE col SET ver = 8"); - } - if (ver < 9) { - col.getDb().execute("UPDATE col SET ver = 9"); - } - if (ver < 10) { - col.getDb().execute("UPDATE cards SET left = left + left * 1000 WHERE queue = " + Consts.QUEUE_TYPE_LRN); - col.getDb().execute("UPDATE col SET ver = 10"); - } - if (ver < 11) { - col.modSchemaNoCheck(); - for (Deck d : col.getDecks().all()) { - if (d.isDyn()) { - int order = d.getInt("order"); - // failed order was removed - if (order >= 5) { - order -= 1; - } - JSONArray terms = new JSONArray(Arrays.asList(d.getString("search"), - d.getInt("limit"), order)); - d.put("terms", new JSONArray()); - d.getJSONArray("terms").put(0, terms); - d.remove("search"); - d.remove("limit"); - d.remove("order"); - d.put("resched", true); - d.put("return", true); - } else { - if (!d.has("extendNew")) { - d.put("extendNew", 10); - d.put("extendRev", 50); - } - } - col.getDecks().save(d); - } - for (DeckConfig c : col.getDecks().allConf()) { - JSONObject r = c.getJSONObject("rev"); - r.put("ivlFct", r.optDouble("ivlFct", 1)); - if (r.has("ivlfct")) { - r.remove("ivlfct"); - } - r.put("maxIvl", 36500); - col.getDecks().save(c); - } - for (Model m : col.getModels().all()) { - JSONArray tmpls = m.getJSONArray("tmpls"); - for (JSONObject t: tmpls.jsonObjectIterable()) { - t.put("bqfmt", ""); - t.put("bafmt", ""); - } - col.getModels().save(m); - } - col.getDb().execute("update col set ver = 11"); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - - private static void _upgradeClozeModel(Collection col, Model m) throws ConfirmModSchemaException { - m.put("type", Consts.MODEL_CLOZE); - // convert first template - JSONObject t = m.getJSONArray("tmpls").getJSONObject(0); - for (String type : new String[] { "qfmt", "afmt" }) { - //noinspection RegExpRedundantEscape // In Android, } should be escaped - t.put(type, t.getString(type).replaceAll("\\{\\{cloze:1:(.+?)\\}\\}", "{{cloze:$1}}")); - } - t.put("name", "Cloze"); - // delete non-cloze cards for the model - JSONArray tmpls = m.getJSONArray("tmpls"); - ArrayList rem = new ArrayList<>(); - for (JSONObject ta: tmpls.jsonObjectIterable()) { - if (!ta.getString("afmt").contains("{{cloze:")) { - rem.add(ta); - } - } - for (JSONObject r : rem) { - col.getModels().remTemplate(m, r); - } - JSONArray newTmpls = new JSONArray(); - newTmpls.put(tmpls.getJSONObject(0)); - m.put("tmpls", newTmpls); - Models._updateTemplOrds(m); - col.getModels().save(m); - - } - - - private static int _createDB(DB db, @NonNull Time time, DroidBackend backend) { - if (backend.databaseCreationCreatesSchema()) { - if (!backend.databaseCreationInitializesData()) { - _setColVars(db, time); - } - // This line is required for testing - otherwise Rust will override a mocked time. - db.execute("update col set crt = ?", UIUtils.getDayStart(time) / 1000); - } else { - db.execute("PRAGMA page_size = 4096"); - db.execute("PRAGMA legacy_file_format = 0"); - db.execute("VACUUM"); - _addSchema(db, true, time); - _updateIndices(db); - } - - db.execute("ANALYZE"); - return Consts.SCHEMA_VERSION; - } - - - private static void _addSchema(DB db, boolean setColConf, @NonNull Time time) { - db.execute("create table if not exists col ( " + "id integer primary key, " - + "crt integer not null," + "mod integer not null," - + "scm integer not null," + "ver integer not null," - + "dty integer not null," + "usn integer not null," - + "ls integer not null," + "conf text not null," - + "models text not null," + "decks text not null," - + "dconf text not null," + "tags text not null" + ");"); - db.execute("create table if not exists notes (" + " id integer primary key, /* 0 */" - + " guid text not null, /* 1 */" + " mid integer not null, /* 2 */" - + " mod integer not null, /* 3 */" + " usn integer not null, /* 4 */" - + " tags text not null, /* 5 */" + " flds text not null, /* 6 */" - + " sfld integer not null, /* 7 */" + " csum integer not null, /* 8 */" - + " flags integer not null, /* 9 */" + " data text not null /* 10 */" + ");"); - db.execute("create table if not exists cards (" + " id integer primary key, /* 0 */" - + " nid integer not null, /* 1 */" + " did integer not null, /* 2 */" - + " ord integer not null, /* 3 */" + " mod integer not null, /* 4 */" - + " usn integer not null, /* 5 */" + " type integer not null, /* 6 */" - + " queue integer not null, /* 7 */" + " due integer not null, /* 8 */" - + " ivl integer not null, /* 9 */" + " factor integer not null, /* 10 */" - + " reps integer not null, /* 11 */" + " lapses integer not null, /* 12 */" - + " left integer not null, /* 13 */" + " odue integer not null, /* 14 */" - + " odid integer not null, /* 15 */" + " flags integer not null, /* 16 */" - + " data text not null /* 17 */" + ");"); - db.execute("create table if not exists revlog (" + " id integer primary key," - + " cid integer not null," + " usn integer not null," - + " ease integer not null," + " ivl integer not null," - + " lastIvl integer not null," + " factor integer not null," - + " time integer not null," + " type integer not null" + ");"); - db.execute("create table if not exists graves (" + " usn integer not null," - + " oid integer not null," + " type integer not null" + ")"); - db.execute("INSERT OR IGNORE INTO col VALUES(1,0,0," + - time.intTimeMS() + "," + Consts.SCHEMA_VERSION + - ",0,0,0,'','{}','','','{}')"); - if (setColConf) { + private static void _createDB(DB db, @NonNull Time time, DroidBackend backend) { + if (!backend.databaseCreationInitializesData()) { _setColVars(db, time); } + // This line is required for testing - otherwise Rust will override a mocked time. + db.execute("update col set crt = ?", UIUtils.getDayStart(time) / 1000); } - private static void _setColVars(DB db, @NonNull Time time) { JSONObject g = new JSONObject(Decks.DEFAULT_DECK); g.put("id", 1); @@ -416,18 +189,22 @@ public static boolean isInMemory() { return sUseInMemory; } - /** Allows the collection to be opened */ + + /** + * Allows the collection to be opened + */ public static void unlockCollection() { sIsLocked = false; Timber.i("unlocked collection"); } + /** * Stops the collection from being opened via throwing {@link SQLiteDatabaseLockedException}. * does not affect a currently open collection - * + *

* To ensure that the collection is locked and unopenable: - * + *

* * Lock the collection * * Get an instance of the collection, if it succeeds, close it * * Ensure the collection is locked by trying to open it, it should fail. @@ -439,6 +216,7 @@ public static void lockCollection() { Timber.i("locked collection. Opening will throw SQLiteDatabaseLockedException"); } + /** * Whether the collection can be opened. If true, {@link #Collection(Context, String, boolean, boolean, Time)} * throws a {@link SQLiteDatabaseLockedException} diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt index 9fda142df05d..07427f2dc0d0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt @@ -39,8 +39,6 @@ interface DroidBackend { fun openCollectionDatabase(path: String): DB fun closeCollection(db: DB?, downgradeToSchema11: Boolean) - /** Whether a call to [DroidBackend.openCollectionDatabase] will generate a schema and indices for the database */ - fun databaseCreationCreatesSchema(): Boolean fun databaseCreationInitializesData(): Boolean fun isUsingRustBackend(): Boolean diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt index df904625e23a..e12f11217632 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt @@ -45,10 +45,6 @@ open class RustDroidBackend( db?.close() } - override fun databaseCreationCreatesSchema(): Boolean { - return true - } - /** Whether the 'Decks' , 'Deck Config', 'Note Types' etc.. are set by database creation */ override fun databaseCreationInitializesData(): Boolean { return false // only true in V16, not V11