Force a regular backup before all DB upgrades, and drop versioned backups

Versioned backups (zotero.sqlite.<version>.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.
This commit is contained in:
Dan Stillman 2026-07-15 16:59:22 -04:00
parent 02fb0e92ed
commit ea2ee7391d
3 changed files with 29 additions and 14 deletions

View file

@ -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.<userdata version>.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.<n>.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);

View file

@ -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);
}

View file

@ -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'))
);
});
});
})