Choose most recently used data directory when upgrading to 5.0

Previously, if someone was upgrading from Zotero for Firefox to 5.0 but
at some point had installed Zotero Standalone and told it not to share
the data directory, 5.0 would use the data directory from the Zotero
profile directory, and people would end up with an empty database with a
Zotero Quick Start Guide from years ago.

This checks for the database with the most recent mtime and uses that
data directory instead. In the Firefox profile, that can be either the
default 'zotero' subdirectory or a custom data directory. If one of the
Firefox locations is used, Zotero prefs are read from Firefox, because
it means that Zotero for Firefox was being used (because if Zotero
Standalone had been in use it would've needed a custom data dir setting
pointing at the Firefox database).
This commit is contained in:
Dan Stillman 2017-08-10 03:31:56 +02:00
parent d3833b8afd
commit 39bfeac86c
2 changed files with 123 additions and 55 deletions

View file

@ -52,7 +52,8 @@ Zotero.DataDirectory = {
init: Zotero.Promise.coroutine(function* () { init: Zotero.Promise.coroutine(function* () {
var file; var dataDir;
var dbFilename = this.getDatabaseFilename();
if (Zotero.Prefs.get('useDataDir')) { if (Zotero.Prefs.get('useDataDir')) {
let prefVal = Zotero.Prefs.get('dataDir'); let prefVal = Zotero.Prefs.get('dataDir');
// Convert old persistent descriptor pref to string path and clear obsolete lastDataDir pref // Convert old persistent descriptor pref to string path and clear obsolete lastDataDir pref
@ -84,61 +85,57 @@ Zotero.DataDirectory = {
// interrupted before the database could be moved (or moving failed), so use the source // interrupted before the database could be moved (or moving failed), so use the source
// directory specified in the marker file. // directory specified in the marker file.
let migrationMarker = OS.Path.join(prefVal, this.MIGRATION_MARKER); let migrationMarker = OS.Path.join(prefVal, this.MIGRATION_MARKER);
let dbFile = OS.Path.join(prefVal, this.getDatabaseFilename()); let dbFile = OS.Path.join(prefVal, dbFilename);
if ((yield OS.File.exists(migrationMarker)) && !(yield OS.File.exists(dbFile))) { if ((yield OS.File.exists(migrationMarker)) && !(yield OS.File.exists(dbFile))) {
let contents = yield Zotero.File.getContentsAsync(migrationMarker); let contents = yield Zotero.File.getContentsAsync(migrationMarker);
try { try {
let { sourceDir } = JSON.parse(contents); let { sourceDir } = JSON.parse(contents);
file = OS.Path.normalize(sourceDir); dataDir = OS.Path.normalize(sourceDir);
} }
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
Zotero.debug(`Invalid marker file:\n\n${contents}`, 1); Zotero.debug(`Invalid marker file:\n\n${contents}`, 1);
e = { name: "NS_ERROR_FILE_NOT_FOUND" }; throw { name: "NS_ERROR_FILE_NOT_FOUND" };
throw e;
} }
} }
else { else {
try { try {
file = OS.Path.normalize(prefVal); dataDir = OS.Path.normalize(prefVal);
} }
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
Zotero.debug(`Invalid path '${prefVal}' in dataDir pref`, 1); Zotero.debug(`Invalid path '${prefVal}' in dataDir pref`, 1);
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
throw e;
}
}
}
if (!(yield OS.File.exists(file)) && file != this.defaultDir) {
// If set to a legacy directory that doesn't exist, forget about it and just use the
// new default location, which will either exist or be created below. The most likely
// cause of this is a migration, so don't bother looking in other-app profiles.
if (this.isLegacy(file)) {
let newDefault = this.defaultDir;
Zotero.debug(`Legacy data directory ${file} from pref not found `
+ `-- reverting to ${newDefault}`, 1);
file = newDefault;
this.set(newDefault);
}
// For other custom directories that don't exist, show not-found dialog
else {
Zotero.debug(`Custom data directory ${file} not found`, 1);
throw { name: "NS_ERROR_FILE_NOT_FOUND" }; throw { name: "NS_ERROR_FILE_NOT_FOUND" };
} }
} }
} }
else {
let dataDir = this.defaultDir;
// if (!(yield OS.File.exists(dataDir)) && dataDir != this.defaultDir) {
// TODO: asyncify // If set to a legacy directory that doesn't exist, forget about it and just use the
// // new default location, which will either exist or be created below. The most likely
// cause of this is a migration, so don't bother looking in other-app profiles.
if (this.isLegacy(dataDir)) {
let newDefault = this.defaultDir;
Zotero.debug(`Legacy data directory ${dataDir} from pref not found `
+ `-- reverting to ${newDefault}`, 1);
dataDir = newDefault;
this.set(newDefault);
}
// For other custom directories that don't exist, show not-found dialog
else {
Zotero.debug(`Custom data directory ${dataDir} not found`, 1);
throw { name: "NS_ERROR_FILE_NOT_FOUND" };
}
}
}
// New installation of 5.0+ with no data directory specified, so check all the places the data
// could be
else {
dataDir = this.defaultDir;
// Check for ~/Zotero/zotero.sqlite // Check for ~/Zotero/zotero.sqlite
let dbFile = OS.Path.join(dataDir, this.getDatabaseFilename()); let dbFile = OS.Path.join(dataDir, dbFilename);
if (yield OS.File.exists(dbFile)) { if (yield OS.File.exists(dbFile)) {
Zotero.debug("Using data directory " + dataDir); Zotero.debug("Using data directory " + dataDir);
this._cache(dataDir); this._cache(dataDir);
@ -149,36 +146,47 @@ Zotero.DataDirectory = {
return dataDir; return dataDir;
} }
// Check for 'zotero' dir in profile dir let useProfile = false;
let profileSubdir = OS.Path.join(Zotero.Profile.dir, this.legacyDirName); let useFirefoxProfile = false;
if (yield OS.File.exists(profileSubdir)) { let useFirefoxProfileCustom = false;
Zotero.debug("Using data directory " + profileSubdir);
this._cache(profileSubdir); // Check for <profile dir>/zotero/zotero.sqlite
return profileSubdir; let profileSubdirModTime;
try {
let dir = OS.Path.join(Zotero.Profile.dir, this.legacyDirName);
let dbFile = OS.Path.join(dir, dbFilename);
profileSubdirModTime = (yield OS.File.stat(dbFile)).lastModificationDate;
Zotero.debug(`Database found at ${dbFile}, last modified ${profileSubdirModTime}`);
dataDir = dir;
useProfile = true;
}
catch (e) {
if (!(e instanceof OS.File.Error && e.becauseNoSuchFile)) {
throw e;
}
} }
// //
// If Standalone and no directory yet, check Firefox directory, or vice versa // Check Firefox directory
// //
let profilesParent = OS.Path.dirname(Zotero.Profile.getOtherAppProfilesDir()); let profilesParent = OS.Path.dirname(Zotero.Profile.getOtherAppProfilesDir());
Zotero.debug("Looking for existing profile in " + profilesParent); Zotero.debug("Looking for Firefox profile in " + profilesParent);
// get default profile // get default profile
var defProfile; var defProfile;
try { try {
defProfile = yield Zotero.Profile.getDefaultInProfilesDir(profilesParent); defProfile = yield Zotero.Profile.getDefaultInProfilesDir(profilesParent);
} catch(e) { }
Zotero.debug("An error occurred locating the Firefox profile; not "+ catch (e) {
"attempting to migrate from Zotero for Firefox"); Zotero.debug("An error occurred locating the Firefox profile; "
+ "not attempting to migrate from Zotero for Firefox");
Zotero.logError(e); Zotero.logError(e);
} }
if (defProfile) {
if(defProfile) {
// get Zotero directory
let profileDir = defProfile[0]; let profileDir = defProfile[0];
Zotero.debug("Found default profile at " + profileDir); Zotero.debug("Found default profile at " + profileDir);
// copy prefs // Read in prefs
let prefsFile = OS.Path.join(profileDir, "prefs.js"); let prefsFile = OS.Path.join(profileDir, "prefs.js");
if (yield OS.File.exists(prefsFile)) { if (yield OS.File.exists(prefsFile)) {
// build sandbox // build sandbox
@ -197,8 +205,76 @@ Zotero.DataDirectory = {
// evaluate // evaluate
Components.utils.evalInSandbox(prefsJs, sandbox); Components.utils.evalInSandbox(prefsJs, sandbox);
var prefs = sandbox.prefs; var prefs = sandbox.prefs;
for(var key in prefs) {
if(key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) === ZOTERO_CONFIG.PREF_BRANCH // Check for data dir pref
if (prefs['extensions.zotero.dataDir'] && prefs['extensions.zotero.useDataDir']) {
Zotero.debug(`Found custom dataDir of ${prefs['extensions.zotero.dataDir']}`);
let nsIFile;
try {
nsIFile = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
nsIFile.persistentDescriptor = prefs['extensions.zotero.dataDir'];
}
catch (e) {
Zotero.logError(e);
if (!useProfile) {
Zotero.debug("Persistent descriptor in extensions.zotero.dataDir "
+ "did not resolve", 1);
throw { name: "NS_ERROR_FILE_NOT_FOUND" };
}
}
if (nsIFile) {
try {
let dbFile = OS.Path.join(nsIFile.path, dbFilename);
let mtime = (yield OS.File.stat(dbFile)).lastModificationDate;
Zotero.debug(`Database found at ${dbFile}, last modified ${mtime}`);
// If custom location has a newer DB, use that
if (useProfile && mtime > profileSubdirModTime) {
dataDir = nsIFile.path;
useFirefoxProfileCustom = true;
useProfile = false;
}
}
catch (e) {
Zotero.logError(e);
// If we have a DB in the Zotero profile and get an error trying to
// access the custom location in Firefox, use the Zotero profile, since
// there's at least some chance it's right. Otherwise, throw an error.
if (!useProfile) {
throw e;
}
}
}
}
// If no custom dir specified, check for a subdirectory
else {
try {
let dir = OS.Path.join(profileDir, this.legacyDirName);
let dbFile = OS.Path.join(dir, dbFilename);
let mtime = (yield OS.File.stat(dbFile)).lastModificationDate;
Zotero.debug(`Database found at ${dbFile}, last modified ${mtime}`);
// If newer than Zotero profile directory, use this one
if (useProfile && mtime > profileSubdirModTime) {
dataDir = dir;
useFirefoxProfile = true;
useProfile = false;
}
}
catch (e) {
Zotero.logError(e);
// Same as above -- throw error if we don't already have a DB
if (!useProfile) {
throw e;
}
}
}
// If using data directory from Zotero for Firefox, transfer those prefs, because
// the fact that that DB was more recent and wasn't set in the Zotero profile prefs
// means that they were using Firefox.
if (useFirefoxProfile || useFirefoxProfileCustom) {
for (let key in prefs) {
if (key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) === ZOTERO_CONFIG.PREF_BRANCH
&& key !== "extensions.zotero.firstRun2") { && key !== "extensions.zotero.firstRun2") {
Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]); Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]);
} }
@ -209,23 +285,15 @@ Zotero.DataDirectory = {
return this.init(); return this.init();
} }
} }
// If there's a data directory in the default profile for the alternative app, use that
let zoteroDir = OS.Path.join(profileDir, this.legacyDirName);
if (yield OS.File.exists(zoteroDir)) {
this.set(zoteroDir);
file = zoteroDir;
} }
} }
if (!file) { this.set(dataDir);
file = dataDir;
}
} }
Zotero.debug("Using data directory " + file); Zotero.debug("Using data directory " + dataDir);
yield Zotero.File.createDirectoryIfMissingAsync(file); yield Zotero.File.createDirectoryIfMissingAsync(dataDir);
this._cache(file); this._cache(dataDir);
}), }),

View file

@ -291,7 +291,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
} }
catch (e) { catch (e) {
// Zotero dir not found // Zotero dir not found
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') { if ((e instanceof OS.File.Error && e.becauseNoSuchFile) || e.name == 'NS_ERROR_FILE_NOT_FOUND') {
let foundInDefault = false; let foundInDefault = false;
try { try {
foundInDefault = (yield OS.File.exists(Zotero.DataDirectory.defaultDir)) foundInDefault = (yield OS.File.exists(Zotero.DataDirectory.defaultDir))