From ea2ee7391dcc41ed5f0a652c1da3816dcaa83015 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 15 Jul 2026 16:59:22 -0400 Subject: [PATCH] Force a regular backup before all DB upgrades, and drop versioned backups Versioned backups (zotero.sqlite..bak) were kept until the next userdata upgrade, potentially bloating the data directory by gigabytes, while minor upgrades made no backup at all. Now any userdata upgrade, integrity check, or global schema update forces a rotation backup. Since versioned backups ignored backup.numBackups, setting that to 0 now fully disables backups, including before upgrades. --- chrome/content/zotero/xpcom/schema.js | 26 +++++++++++++------------- test/content/support.js | 8 ++++++++ test/tests/schemaTest.js | 9 ++++++++- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 10bd4913ce..82f8aba5b3 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -39,8 +39,8 @@ Zotero.Schema = new function () { const REPOSITORY_CHECK_INTERVAL = 86400; const REPOSITORY_RETRY_INTERVAL = 3600; - // If updating from this userdata version or later, don't show "Upgrading database…" and don't make - // DB backup first. This should be set to false when breaking compatibility or making major changes. + // If updating from this userdata version or later, don't show "Upgrading database…". This + // should be set to false when adding a slow or otherwise major upgrade step. const minorUpdateFrom = 112; var _dbVersions = []; @@ -155,12 +155,10 @@ Zotero.Schema = new function () { var userdataVersion = await _getSchemaSQLVersion('userdata'); options.minor = minorUpdateFrom && userdata >= minorUpdateFrom; - // If non-minor userdata upgrade, make backup of database first - if (userdata < userdataVersion && !options.minor) { - await Zotero.DB.backUpDatabase({ force: true, suffix: userdata }); - } - // Automatic backup - else if (integrityCheckRequired || bundledGlobalSchemaVersionCompare === 1) { + // Force a backup before a userdata upgrade, an integrity check, or a global schema update + if (userdata < userdataVersion + || integrityCheckRequired + || bundledGlobalSchemaVersionCompare === 1) { await Zotero.DB.backUpDatabase({ force: true }); } @@ -264,8 +262,8 @@ Zotero.Schema = new function () { } if (updated) { - // Upgrade seems to have been a success -- delete any previous backups - var maxPrevious = userdata - 1; + // Upgrade seems to have been a success -- delete any versioned backups + // (zotero.sqlite..bak) from upgrades in previous versions var file = Zotero.File.pathToFile(Zotero.DataDirectory.dir); var toDelete = []; try { @@ -276,11 +274,12 @@ Zotero.Schema = new function () { if (file.isDirectory()) { continue; } - var matches = file.leafName.match(/zotero\.sqlite\.([0-9]{2,})\.bak/); + var matches = file.leafName.match(/^zotero\.sqlite\.([0-9]{2,})\.bak$/); if (!matches) { continue; } - if (matches[1]>=28 && matches[1]<=maxPrevious) { + // Rotation backups (zotero.sqlite..bak) only go up to 24 + if (matches[1] >= 28) { toDelete.push(file); } } @@ -3679,7 +3678,8 @@ Zotero.Schema = new function () { await Zotero.DB.queryAsync("DROP TABLE savedSearchConditionsOld"); } - // If breaking compatibility or doing anything dangerous, clear minorUpdateFrom + // If adding a slow or otherwise major upgrade step, clear minorUpdateFrom so that + // "Upgrading database…" is shown } await _updateDBVersion('userdata', toVersion); diff --git a/test/content/support.js b/test/content/support.js index 043dd6cf9f..547d803dd4 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -721,10 +721,18 @@ var removeDir = async function (dir) { * * @param {Object} [options] - Initialization options, as passed to Zotero.init(), overriding * any that were set at startup + * @param {Object} [options.prefs] - Prefs to set for the reinitialization, overriding the + * test-runner defaults. Cleared by the global afterEach(). */ async function resetDB(options = {}) { resetPrefs(); + if (options.prefs) { + for (let key in options.prefs) { + Zotero.Prefs.set(key, options.prefs[key]); + } + } + if (options.thisArg) { options.thisArg.timeout(60000); } diff --git a/test/tests/schemaTest.js b/test/tests/schemaTest.js index 7d7a146df0..bf2fd41abb 100644 --- a/test/tests/schemaTest.js +++ b/test/tests/schemaTest.js @@ -488,11 +488,18 @@ describe("Zotero.Schema", function () { await resetDB({ thisArg: this, skipBundledFiles: true, - dbFile: OS.Path.join(getTestDataDirectory().path, 'zotero-4.0.sqlite.zip') + dbFile: OS.Path.join(getTestDataDirectory().path, 'zotero-4.0.sqlite.zip'), + // Backups are disabled in the test profile + prefs: { 'backup.numBackups': 2 } }); // Make sure we can open the Zotero pane without errors win = await loadZoteroPane(); win.close(); + + // A backup should have been made before the upgrade + assert.isTrue( + await IOUtils.exists(PathUtils.join(Zotero.DataDirectory.dir, 'zotero.sqlite.bak')) + ); }); }); })