Point other profiles to new data dir after migration (+ code reorg)
Look for other profiles, from both apps (Firefox and Standalone), that point to the data directory being migrated and update prefs.js in those profiles to point to the new location. Also reorganize code into Zotero.Profile and Zotero.DataDirectory namespaces
This commit is contained in:
parent
c0bf2b91f6
commit
4c0abb6816
13 changed files with 1338 additions and 919 deletions
|
@ -230,7 +230,7 @@ var ZoteroOverlay = new function()
|
|||
|
||||
// Warn about unsafe data directory on first display
|
||||
let dataDir = Zotero.getZoteroDirectory();
|
||||
Zotero.checkForUnsafeDataDirectory(dataDir.path);
|
||||
Zotero.DataDirectory.checkForUnsafeLocation(dataDir.path); // async
|
||||
|
||||
// Make sure tags splitter isn't missing for people upgrading from <2.0b7
|
||||
document.getElementById('zotero-tags-splitter').collapsed = false;
|
||||
|
|
|
@ -38,8 +38,8 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
|
||||
migrateDataDirectory: Zotero.Promise.coroutine(function* () {
|
||||
var currentDir = Zotero.getZoteroDirectory().path;
|
||||
var defaultDir = Zotero.getDefaultDataDir();
|
||||
var currentDir = Zotero.DataDirectory.dir;
|
||||
var defaultDir = Zotero.DataDirectory.defaultDir;
|
||||
if (currentDir == defaultDir) {
|
||||
Zotero.debug("Already using default directory");
|
||||
return;
|
||||
|
@ -65,7 +65,7 @@ Zotero_Preferences.Advanced = {
|
|||
);
|
||||
|
||||
if (index == 0) {
|
||||
yield Zotero.markDataDirectoryForMigration(currentDir);
|
||||
yield Zotero.DataDirectory.markForMigration(currentDir);
|
||||
Zotero.Utilities.Internal.quitZotero(true);
|
||||
}
|
||||
}),
|
||||
|
@ -215,13 +215,13 @@ Zotero_Preferences.Advanced = {
|
|||
onDataDirLoad: function () {
|
||||
var useDataDir = Zotero.Prefs.get('useDataDir');
|
||||
var dataDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir');
|
||||
var defaultDataDir = Zotero.getDefaultDataDir();
|
||||
var currentDir = Zotero.getZoteroDirectory().path;
|
||||
var currentDir = Zotero.DataDirectory.dir;
|
||||
var defaultDataDir = Zotero.DataDirectory.defaultDir;
|
||||
|
||||
// Change "Use profile directory" label to home directory location unless using profile dir
|
||||
if (useDataDir || currentDir == defaultDataDir) {
|
||||
document.getElementById('default-data-dir').setAttribute(
|
||||
'label', Zotero.getString('dataDir.default', Zotero.getDefaultDataDir())
|
||||
'label', Zotero.getString('dataDir.default', Zotero.DataDirectory.defaultDir)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -232,14 +232,14 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
document.getElementById('data-dir-path').setAttribute('disabled', !useDataDir);
|
||||
document.getElementById('migrate-data-dir').setAttribute(
|
||||
'hidden', !Zotero.canMigrateDataDirectory()
|
||||
'hidden', !Zotero.DataDirectory.canMigrate()
|
||||
);
|
||||
|
||||
return useDataDir;
|
||||
},
|
||||
|
||||
|
||||
onDataDirUpdate: function (event) {
|
||||
onDataDirUpdate: Zotero.Promise.coroutine(function* (event) {
|
||||
var radiogroup = document.getElementById('data-dir');
|
||||
var useDataDir = Zotero.Prefs.get('useDataDir');
|
||||
var newUseDataDir = radiogroup.selectedIndex == 1;
|
||||
|
@ -250,11 +250,13 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
// This call shows a filepicker if needed, forces a restart if required, and does nothing if
|
||||
// cancel was pressed or value hasn't changed
|
||||
Zotero.chooseZoteroDirectory(true, !newUseDataDir, function () {
|
||||
Zotero_Preferences.openURL('https://zotero.org/support/zotero_data');
|
||||
});
|
||||
yield Zotero.DataDirectory.choose(
|
||||
true,
|
||||
!newUseDataDir,
|
||||
() => Zotero_Preferences.openURL('https://zotero.org/support/zotero_data')
|
||||
);
|
||||
radiogroup.selectedIndex = this._usingDefaultDataDir() ? 0 : 1;
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
chooseDataDir: function(event) {
|
||||
|
@ -269,7 +271,7 @@ Zotero_Preferences.Advanced = {
|
|||
var prefValue = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir');
|
||||
|
||||
// Don't show path if the default
|
||||
if (prefValue == Zotero.getDefaultDataDir()) {
|
||||
if (prefValue == Zotero.DataDirectory.defaultDir) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -285,7 +287,7 @@ Zotero_Preferences.Advanced = {
|
|||
|
||||
var dataDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir');
|
||||
// Default home directory location
|
||||
if (dataDir == Zotero.getDefaultDataDir()) {
|
||||
if (dataDir == Zotero.DataDirectory.defaultDir) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@
|
|||
<radiogroup id="data-dir"
|
||||
preference="pref-useDataDir"
|
||||
onsyncfrompreference="return Zotero_Preferences.Advanced.onDataDirLoad()"
|
||||
onsynctopreference="return Zotero_Preferences.Advanced.onDataDirUpdate(event);">
|
||||
onsynctopreference="Zotero_Preferences.Advanced.onDataDirUpdate(event);">
|
||||
<radio id="default-data-dir" label="&zotero.preferences.dataDir.useProfile;" value="false"/>
|
||||
<hbox>
|
||||
<radio label="&zotero.preferences.dataDir.custom;" value="true"/>
|
||||
|
@ -199,7 +199,7 @@
|
|||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dataDir.reveal;"
|
||||
oncommand="Zotero.revealDataDirectory()"/>
|
||||
oncommand="Zotero.DataDirectory.reveal()"/>
|
||||
<button id="migrate-data-dir" label="&zotero.preferences.dataDir.migrate;"
|
||||
oncommand="Zotero_Preferences.Advanced.migrateDataDirectory()" hidden="true"/>
|
||||
</hbox>
|
||||
|
|
847
chrome/content/zotero/xpcom/dataDirectory.js
Normal file
847
chrome/content/zotero/xpcom/dataDirectory.js
Normal file
|
@ -0,0 +1,847 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2016 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
Zotero.DataDirectory = {
|
||||
MIGRATION_MARKER: 'migrate-dir',
|
||||
|
||||
get dir() {
|
||||
if (!this._dir) {
|
||||
throw new Error("Data directory not initialized");
|
||||
}
|
||||
return this._dir;
|
||||
},
|
||||
|
||||
get defaultDir() {
|
||||
// Use special data directory for tests
|
||||
if (Zotero.test) {
|
||||
return OS.Path.join(OS.Path.dirname(OS.Constants.Path.profileDir), "Zotero");
|
||||
}
|
||||
return OS.Path.join(OS.Constants.Path.homeDir, ZOTERO_CONFIG.CLIENT_NAME);
|
||||
},
|
||||
|
||||
get legacyDirName() {
|
||||
return ZOTERO_CONFIG.ID;
|
||||
},
|
||||
|
||||
_dir: null,
|
||||
_warnOnUnsafeLocation: true,
|
||||
|
||||
|
||||
init: Zotero.Promise.coroutine(function* () {
|
||||
var file;
|
||||
if (Zotero.Prefs.get('useDataDir')) {
|
||||
let prefVal = Zotero.Prefs.get('dataDir');
|
||||
// Convert old persistent descriptor pref to string path and clear obsolete lastDataDir pref
|
||||
//
|
||||
// persistentDescriptor now appears to return (and parse) a string path anyway on macOS,
|
||||
// which is the only place where it didn't use a string path to begin with, but be explicit
|
||||
// just in case there's some difference.
|
||||
//
|
||||
// A post-Mozilla prefs migration should do this same check, and then this conditional can
|
||||
// be removed.
|
||||
if (Zotero.Prefs.get('lastDataDir')) {
|
||||
let nsIFile;
|
||||
try {
|
||||
nsIFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
nsIFile.persistentDescriptor = prefVal;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Persistent descriptor in extensions.zotero.dataDir did not resolve", 1);
|
||||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
throw e;
|
||||
}
|
||||
// This removes lastDataDir
|
||||
this.set(nsIFile.path);
|
||||
file = nsIFile.path;
|
||||
}
|
||||
else {
|
||||
// If there's a migration marker in this directory and no database, migration was
|
||||
// interrupted before the database could be moved (or moving failed), so use the source
|
||||
// directory specified in the marker file.
|
||||
let migrationMarker = OS.Path.join(prefVal, this.MIGRATION_MARKER);
|
||||
let dbFile = OS.Path.join(prefVal, this.getDatabaseFilename());
|
||||
if ((yield OS.File.exists(migrationMarker)) && !(OS.File.exists(dbFile))) {
|
||||
let contents = yield Zotero.File.getContentsAsync(migrationMarker);
|
||||
try {
|
||||
let { sourceDir } = JSON.parse(contents);
|
||||
file = OS.Path.normalize(sourceDir);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(`Invalid marker file:\n\n${contents}`, 1);
|
||||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
file = OS.Path.normalize(prefVal);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
let dataDir = this.defaultDir;
|
||||
|
||||
//
|
||||
// TODO: asyncify
|
||||
//
|
||||
|
||||
// Check for ~/Zotero/zotero.sqlite
|
||||
let dbFile = OS.Path.join(dataDir, this.getDatabaseFilename());
|
||||
if (yield OS.File.exists(dbFile)) {
|
||||
Zotero.debug("Using data directory " + dataDir);
|
||||
this._cache(dataDir);
|
||||
|
||||
// Set as a custom data directory so that 4.0 uses it
|
||||
this.set(dataDir);
|
||||
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
// Check for 'zotero' dir in profile dir
|
||||
let profileSubdir = OS.Path.join(Zotero.Profile.dir, this.legacyDirName);
|
||||
if (yield OS.File.exists(profileSubdir)) {
|
||||
Zotero.debug("Using data directory " + profileSubdir);
|
||||
this._cache(profileSubdir);
|
||||
return profileSubdir;
|
||||
}
|
||||
|
||||
//
|
||||
// If Standalone and no directory yet, check Firefox directory, or vice versa
|
||||
//
|
||||
let profilesParent = OS.Path.dirname(Zotero.Profile.getOtherAppProfilesDir());
|
||||
Zotero.debug("Looking for existing profile in " + profilesParent);
|
||||
|
||||
// get default profile
|
||||
var defProfile;
|
||||
try {
|
||||
defProfile = yield Zotero.Profile.getDefaultInProfilesDir(profilesParent);
|
||||
} catch(e) {
|
||||
Zotero.debug("An error occurred locating the Firefox profile; not "+
|
||||
"attempting to migrate from Zotero for Firefox");
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
if(defProfile) {
|
||||
// get Zotero directory
|
||||
let profileDir = defProfile[0].path;
|
||||
Zotero.debug("Found default profile at " + profileDir);
|
||||
|
||||
// copy prefs
|
||||
let prefsFile = OS.Path.join(profileDir, "prefs.js");
|
||||
if (yield OS.File.exists(prefsFile)) {
|
||||
// build sandbox
|
||||
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
|
||||
Components.utils.evalInSandbox(
|
||||
"var prefs = {};"+
|
||||
"function user_pref(key, val) {"+
|
||||
"prefs[key] = val;"+
|
||||
"}"
|
||||
, sandbox);
|
||||
|
||||
// remove comments
|
||||
var prefsJs = yield Zotero.File.getContentsAsync(prefsFile);
|
||||
prefsJs = prefsJs.replace(/^#[^\r\n]*$/mg, "");
|
||||
|
||||
// evaluate
|
||||
Components.utils.evalInSandbox(prefsJs, sandbox);
|
||||
var prefs = sandbox.prefs;
|
||||
for(var key in prefs) {
|
||||
if(key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) === ZOTERO_CONFIG.PREF_BRANCH
|
||||
&& key !== "extensions.zotero.firstRun2") {
|
||||
Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// If data directory setting was transferred, use that
|
||||
if (Zotero.Prefs.get('useDataDir')) {
|
||||
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) {
|
||||
file = dataDir;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Using data directory " + file);
|
||||
yield Zotero.File.createDirectoryIfMissingAsync(file);
|
||||
this._cache(file);
|
||||
}),
|
||||
|
||||
|
||||
_cache: function (dir) {
|
||||
this._dir = dir;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @return {Boolean} - True if the directory changed; false otherwise
|
||||
*/
|
||||
set: function (path) {
|
||||
var origPath = Zotero.Prefs.get('dataDir');
|
||||
|
||||
Zotero.Prefs.set('dataDir', path);
|
||||
// Clear legacy pref
|
||||
Zotero.Prefs.clear('lastDataDir');
|
||||
Zotero.Prefs.set('useDataDir', true);
|
||||
|
||||
return path != origPath;
|
||||
},
|
||||
|
||||
|
||||
choose: Zotero.Promise.coroutine(function* (forceQuitNow, useHomeDir, moreInfoCallback) {
|
||||
var win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
var ps = Services.prompt;
|
||||
|
||||
if (useHomeDir) {
|
||||
let changed = this.set(this.defaultDir);
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
while (true) {
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(win, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
|
||||
fp.displayDirectory = Zotero.File.pathToFile(this.dir);
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() == nsIFilePicker.returnOK) {
|
||||
var file = fp.file;
|
||||
let dialogText = '';
|
||||
let dialogTitle = '';
|
||||
|
||||
if (file.path == (Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'))) {
|
||||
Zotero.debug("Data directory hasn't changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// In dropbox folder
|
||||
if (Zotero.File.isDropboxDirectory(file.path)) {
|
||||
dialogTitle = Zotero.getString('general.warning');
|
||||
dialogText = Zotero.getString('dataDir.unsafeLocation.selected.dropbox') + "\n\n"
|
||||
+ Zotero.getString('dataDir.unsafeLocation.selected.useAnyway');
|
||||
}
|
||||
else if (file.directoryEntries.hasMoreElements()) {
|
||||
let dbfile = file.clone();
|
||||
dbfile.append(this.getDatabaseFilename());
|
||||
|
||||
// Warn if non-empty and no zotero.sqlite
|
||||
if (!dbfile.exists()) {
|
||||
dialogTitle = Zotero.getString('dataDir.selectedDirNonEmpty.title');
|
||||
dialogText = Zotero.getString('dataDir.selectedDirNonEmpty.text');
|
||||
}
|
||||
}
|
||||
// Directory empty
|
||||
else {
|
||||
dialogTitle = Zotero.getString('dataDir.selectedDirEmpty.title');
|
||||
dialogText = Zotero.getString('dataDir.selectedDirEmpty.text', Zotero.appName) + '\n\n'
|
||||
+ Zotero.getString('dataDir.selectedDirEmpty.useNewDir');
|
||||
}
|
||||
// Warning dialog to be displayed
|
||||
if(dialogText !== '') {
|
||||
let buttonFlags = ps.STD_YES_NO_BUTTONS;
|
||||
if (moreInfoCallback) {
|
||||
buttonFlags += ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
|
||||
}
|
||||
let index = ps.confirmEx(null,
|
||||
dialogTitle,
|
||||
dialogText,
|
||||
buttonFlags,
|
||||
null,
|
||||
null,
|
||||
moreInfoCallback ? Zotero.getString('general.moreInformation') : null,
|
||||
null, {});
|
||||
|
||||
// Not OK -- return to file picker
|
||||
if (index == 1) {
|
||||
continue;
|
||||
}
|
||||
else if (index == 2) {
|
||||
setTimeout(function () {
|
||||
moreInfoCallback();
|
||||
}, 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.set(file.path);
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
if (!forceQuitNow) {
|
||||
buttonFlags += (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
}
|
||||
var app = Zotero.appName;
|
||||
var index = ps.confirmEx(null,
|
||||
Zotero.getString('general.restartRequired'),
|
||||
Zotero.getString('general.restartRequiredForChange', app)
|
||||
+ "\n\n" + Zotero.getString('dataDir.moveFilesToNewLocation', app),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.quitApp', app),
|
||||
forceQuitNow ? null : Zotero.getString('general.restartLater'),
|
||||
null, null, {});
|
||||
|
||||
if (forceQuitNow || index == 0) {
|
||||
Services.startup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
|
||||
return useHomeDir ? true : file;
|
||||
}),
|
||||
|
||||
|
||||
forceChange: function (win) {
|
||||
if (!win) {
|
||||
win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
}
|
||||
var ps = Services.prompt;
|
||||
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
while (true) {
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(win, Zotero.getString('dataDir.selectNewDir', Zotero.clientName), nsIFilePicker.modeGetFolder);
|
||||
fp.displayDirectory = Zotero.File.pathToFile(this.dir);
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() == nsIFilePicker.returnOK) {
|
||||
var file = fp.file;
|
||||
|
||||
if (file.directoryEntries.hasMoreElements()) {
|
||||
ps.alert(null,
|
||||
Zotero.getString('dataDir.mustSelectEmpty.title'),
|
||||
Zotero.getString('dataDir.mustSelectEmpty.text')
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.set(file.path);
|
||||
|
||||
return file;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
checkForUnsafeLocation: Zotero.Promise.coroutine(function* (path) {
|
||||
if (this._warnOnUnsafeLocation && Zotero.File.isDropboxDirectory(path)
|
||||
&& Zotero.Prefs.get('warnOnUnsafeDataDir')) {
|
||||
this._warnOnUnsafeLocation = false;
|
||||
let check = {value: false};
|
||||
let index = Services.prompt.confirmEx(
|
||||
null,
|
||||
Zotero.getString('general.warning'),
|
||||
Zotero.getString('dataDir.unsafeLocation.existing.dropbox') + "\n\n"
|
||||
+ Zotero.getString('dataDir.unsafeLocation.existing.chooseDifferent'),
|
||||
Services.prompt.STD_YES_NO_BUTTONS,
|
||||
null, null, null,
|
||||
Zotero.getString('general.dontShowWarningAgain'),
|
||||
check
|
||||
);
|
||||
|
||||
// Yes - display dialog.
|
||||
if (index == 0) {
|
||||
yield this.choose(true);
|
||||
}
|
||||
if (check.value) {
|
||||
Zotero.Prefs.set('warnOnUnsafeDataDir', false);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
isLegacy: function (dir) {
|
||||
// 'zotero'
|
||||
return OS.Path.basename(dir) == this.legacyDirName
|
||||
// '69pmactz.default'
|
||||
&& OS.Path.basename(OS.Path.dirname(dir)).match(/^[0-9a-z]{8}\..+/)
|
||||
// 'Profiles'
|
||||
&& OS.Path.basename(OS.Path.dirname(OS.Path.dirname(dir))) == 'Profiles';
|
||||
},
|
||||
|
||||
|
||||
canMigrate: function () {
|
||||
// If (not default location) && (not useDataDir or within legacy location)
|
||||
var currentDir = this.dir;
|
||||
if (currentDir == this.defaultDir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Legacy default or set to legacy default from other program (Standalone/Z4Fx) to share data
|
||||
if (!Zotero.Prefs.get('useDataDir') || this.isLegacy(currentDir)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
reveal: function () {
|
||||
return Zotero.File.reveal(this.dir);
|
||||
},
|
||||
|
||||
|
||||
markForMigration: function (dir, automatic = false) {
|
||||
return Zotero.File.putContentsAsync(
|
||||
OS.Path.join(dir, this.MIGRATION_MARKER),
|
||||
JSON.stringify({
|
||||
sourceDir: dir,
|
||||
automatic
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Migrate data directory if necessary and show any errors
|
||||
*
|
||||
* @param {String} dataDir - Current directory
|
||||
* @param {String} targetDir - Target directory, which may be the same; except in tests, this is
|
||||
* the default data directory
|
||||
*/
|
||||
checkForMigration: Zotero.Promise.coroutine(function* (dataDir, newDir) {
|
||||
if (!this.canMigrate(dataDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let migrationMarker = OS.Path.join(dataDir, this.MIGRATION_MARKER);
|
||||
try {
|
||||
var exists = yield OS.File.exists(migrationMarker)
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
let automatic = false;
|
||||
if (!exists) {
|
||||
// Migrate automatically on macOS and Linux -- this should match the check in
|
||||
// Zotero.File.moveDirectory()
|
||||
if (!Zotero.isWin && (yield OS.File.exists("/bin/mv"))) {
|
||||
automatic = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an existing pipe from other running versions of Zotero pointing at the same data
|
||||
// directory, and skip migration if found
|
||||
try {
|
||||
let foundPipe = yield Zotero.IPC.pipeExists();
|
||||
if (foundPipe) {
|
||||
Zotero.debug("Found existing pipe -- skipping migration");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error checking for pipe -- skipping migration:\n\n" + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are other profiles pointing to the old directory, make sure we can edit the prefs.js
|
||||
// file before doing anything, or else we risk orphaning a 4.0 installation
|
||||
try {
|
||||
let otherProfiles = yield Zotero.Profile.findOtherProfilesUsingDataDirectory(dataDir);
|
||||
// 'touch' each prefs.js file to make sure we can access it
|
||||
for (let dir of otherProfiles) {
|
||||
let prefs = OS.Path.join(dir, "prefs.js");
|
||||
yield OS.File.setDates(prefs);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.logError("Error checking other profiles -- skipping migration");
|
||||
// TODO: After 5.0 has been out a while, remove this and let migration continue even if
|
||||
// other profile directories can't be altered, with the assumption that they'll be running
|
||||
// 5.0 already and will be pick up the new data directory automatically.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (automatic) {
|
||||
yield this.markForMigration(dataDir, true);
|
||||
}
|
||||
|
||||
let sourceDir;
|
||||
let oldDir;
|
||||
let partial = false;
|
||||
|
||||
// Check whether this is an automatic or manual migration
|
||||
let contents;
|
||||
try {
|
||||
contents = yield Zotero.File.getContentsAsync(migrationMarker);
|
||||
({ sourceDir, automatic } = JSON.parse(contents));
|
||||
}
|
||||
catch (e) {
|
||||
if (contents !== undefined) {
|
||||
Zotero.debug(contents, 1);
|
||||
}
|
||||
Zotero.logError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not set to the default directory, so use current as old directory
|
||||
if (dataDir != newDir) {
|
||||
oldDir = dataDir;
|
||||
}
|
||||
// Unfinished migration -- already using new directory, so get path to previous
|
||||
// directory from the migration marker
|
||||
else {
|
||||
oldDir = sourceDir;
|
||||
partial = true;
|
||||
}
|
||||
|
||||
// Not yet used
|
||||
let progressHandler = function (progress, progressMax) {
|
||||
this.updateZoteroPaneProgressMeter(Math.round(progress / progressMax));
|
||||
}.bind(this);
|
||||
|
||||
let errors;
|
||||
let mode = automatic ? 'automatic' : 'manual';
|
||||
// This can seemingly fail due to a race condition building the Standalone window,
|
||||
// so just ignore it if it does
|
||||
try {
|
||||
Zotero.showZoteroPaneProgressMeter(Zotero.getString("dataDir.migration.inProgress"));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
try {
|
||||
errors = yield this.migrate(oldDir, newDir, partial, progressHandler);
|
||||
}
|
||||
catch (e) {
|
||||
// Complete failure (failed to create new directory, copy marker, or move database)
|
||||
Zotero.debug("Migration failed", 1);
|
||||
Zotero.logError(e);
|
||||
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
let index = ps.confirmEx(null,
|
||||
Zotero.getString('dataDir.migration.failure.title'),
|
||||
Zotero.getString(`dataDir.migration.failure.full.${mode}.text1`, ZOTERO_CONFIG.CLIENT_NAME)
|
||||
+ "\n\n"
|
||||
+ e
|
||||
+ "\n\n"
|
||||
+ Zotero.getString(`dataDir.migration.failure.full.${mode}.text2`, Zotero.appName)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.full.current', oldDir)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.full.recommended', newDir),
|
||||
buttonFlags,
|
||||
Zotero.getString('dataDir.migration.failure.full.showCurrentDirectoryAndQuit', Zotero.appName),
|
||||
Zotero.getString('general.notNow'),
|
||||
null, null, {}
|
||||
);
|
||||
if (index == 0) {
|
||||
yield Zotero.File.reveal(oldDir);
|
||||
Zotero.skipLoading = true;
|
||||
Zotero.Utilities.Internal.quitZotero();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set data directory again
|
||||
Zotero.debug("Using new data directory " + newDir);
|
||||
this._cache(newDir);
|
||||
// Tell Zotero for Firefox in connector mode to reload and find the new data directory
|
||||
if (Zotero.isStandalone) {
|
||||
Zotero.IPC.broadcast('reinit');
|
||||
}
|
||||
|
||||
// At least the database was copied, but other things failed
|
||||
if (errors.length) {
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
let index = ps.confirmEx(null,
|
||||
Zotero.getString('dataDir.migration.failure.title'),
|
||||
Zotero.getString(`dataDir.migration.failure.partial.${mode}.text`,
|
||||
[ZOTERO_CONFIG.CLIENT_NAME, Zotero.appName])
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.partial.old', oldDir)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.partial.new', newDir),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.tryAgain'),
|
||||
Zotero.getString('general.tryLater'),
|
||||
Zotero.getString('dataDir.migration.failure.partial.showDirectoriesAndQuit', Zotero.appName),
|
||||
null, {}
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
return this.checkForMigration(newDir, newDir);
|
||||
}
|
||||
// Focus the first file/folder in the old directory
|
||||
else if (index == 2) {
|
||||
try {
|
||||
let it = new OS.File.DirectoryIterator(oldDir);
|
||||
let entry;
|
||||
try {
|
||||
entry = yield it.next();
|
||||
}
|
||||
catch (e) {
|
||||
if (e != StopIteration) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
it.close();
|
||||
}
|
||||
if (entry) {
|
||||
yield Zotero.File.reveal(entry.path);
|
||||
}
|
||||
// Focus the database file in the new directory
|
||||
yield Zotero.File.reveal(OS.Path.join(newDir, this.getDatabaseFilename()));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
Zotero.skipLoading = true;
|
||||
Zotero.Utilities.Internal.quitZotero();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Recursively moves data directory from one location to another and updates the data directory
|
||||
* setting in this profile and any profiles pointing to the old location
|
||||
*
|
||||
* If moving the database file fails, an error is thrown.
|
||||
* Otherwise, an array of errors is returned.
|
||||
*
|
||||
* @param {String} oldDir
|
||||
* @param {String} newDir
|
||||
* @return {Error[]}
|
||||
*/
|
||||
migrate: Zotero.Promise.coroutine(function* (oldDir, newDir, partial) {
|
||||
var dbName = this.getDatabaseFilename();
|
||||
var errors = [];
|
||||
|
||||
function addError(e) {
|
||||
errors.push(e);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
if (!(yield OS.File.exists(oldDir))) {
|
||||
Zotero.debug(`Old directory ${oldDir} doesn't exist -- nothing to migrate`);
|
||||
try {
|
||||
let newMigrationMarker = OS.Path.join(newDir, this.MIGRATION_MARKER);
|
||||
Zotero.debug("Removing " + newMigrationMarker);
|
||||
yield OS.File.remove(newMigrationMarker);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
if (partial) {
|
||||
Zotero.debug(`Continuing data directory migration from ${oldDir} to ${newDir}`);
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`Migrating data directory from ${oldDir} to ${newDir}`);
|
||||
}
|
||||
|
||||
// Create the new directory
|
||||
if (!partial) {
|
||||
try {
|
||||
yield OS.File.makeDir(
|
||||
newDir,
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
// If default dir exists and is non-empty, move it out of the way
|
||||
// ("Zotero-1", "Zotero-2", …)
|
||||
if (e instanceof OS.File.Error && e.becauseExists) {
|
||||
if (!(yield Zotero.File.directoryIsEmpty(newDir))) {
|
||||
let i = 1;
|
||||
while (true) {
|
||||
let backupDir = newDir + "-" + i++;
|
||||
if (yield OS.File.exists(backupDir)) {
|
||||
if (i > 5) {
|
||||
throw new Error("Too many backup directories "
|
||||
+ "-- stopped at " + backupDir);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Zotero.debug(`Moving existing directory to ${backupDir}`);
|
||||
yield Zotero.File.moveDirectory(newDir, backupDir);
|
||||
break;
|
||||
}
|
||||
yield OS.File.makeDir(
|
||||
newDir,
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy marker
|
||||
let oldMarkerFile = OS.Path.join(oldDir, this.MIGRATION_MARKER);
|
||||
// Marker won't exist on subsequent attempts after partial failure
|
||||
if (yield OS.File.exists(oldMarkerFile)) {
|
||||
yield OS.File.copy(oldMarkerFile, OS.Path.join(newDir, this.MIGRATION_MARKER));
|
||||
}
|
||||
|
||||
// Update the data directory setting first. If moving the database fails, get() will continue
|
||||
// to use the old directory based on the migration marker
|
||||
this.set(newDir);
|
||||
|
||||
// Move database
|
||||
if (!partial) {
|
||||
Zotero.debug("Moving " + dbName);
|
||||
yield OS.File.move(OS.Path.join(oldDir, dbName), OS.Path.join(newDir, dbName));
|
||||
}
|
||||
|
||||
// Once the database has been moved, we can clear the migration marker from the old directory.
|
||||
// If the migration is interrupted after this, it can be continued later based on the migration
|
||||
// marker in the new directory.
|
||||
try {
|
||||
yield OS.File.remove(OS.Path.join(oldDir, this.MIGRATION_MARKER));
|
||||
}
|
||||
catch (e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
errors = errors.concat(yield Zotero.File.moveDirectory(
|
||||
oldDir,
|
||||
newDir,
|
||||
{
|
||||
allowExistingTarget: true,
|
||||
// Don't overwrite root files (except for hidden files like .DS_Store)
|
||||
noOverwrite: path => {
|
||||
return OS.Path.dirname(path) == oldDir && !OS.Path.basename(path).startsWith('.')
|
||||
},
|
||||
}
|
||||
));
|
||||
|
||||
if (errors.length) {
|
||||
Zotero.logError("Not all files were transferred from " + oldDir + " to " + newDir);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
let newMigrationMarker = OS.Path.join(newDir, this.MIGRATION_MARKER);
|
||||
Zotero.debug("Removing " + newMigrationMarker);
|
||||
yield OS.File.remove(newMigrationMarker);
|
||||
|
||||
Zotero.debug("Migration successful");
|
||||
}
|
||||
catch (e) {
|
||||
addError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update setting in other profiles that point to this data directory
|
||||
try {
|
||||
let otherProfiles = yield Zotero.Profile.findOtherProfilesUsingDataDirectory(oldDir);
|
||||
for (let dir of otherProfiles) {
|
||||
try {
|
||||
yield Zotero.Profile.updateProfileDataDirectory(dir, oldDir, newDir);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error updating " + OS.Path.join(dir.path, "prefs.js"));
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error updating other profiles to point to new location");
|
||||
}
|
||||
|
||||
return errors;
|
||||
}),
|
||||
|
||||
|
||||
getDatabaseFilename: function (name) {
|
||||
return (name || ZOTERO_CONFIG.ID) + '.sqlite';
|
||||
},
|
||||
|
||||
getDatabase: function (name, ext) {
|
||||
name = this.getDatabaseFilename(name);
|
||||
ext = ext ? '.' + ext : '';
|
||||
|
||||
return OS.Path.join(this.dir, name + ext);
|
||||
}
|
||||
};
|
|
@ -444,7 +444,6 @@ Zotero.File = new function(){
|
|||
var it = new OS.File.DirectoryIterator(path);
|
||||
try {
|
||||
let entry = yield it.next();
|
||||
Zotero.debug(entry);
|
||||
return false;
|
||||
}
|
||||
catch (e) {
|
||||
|
|
236
chrome/content/zotero/xpcom/profile.js
Normal file
236
chrome/content/zotero/xpcom/profile.js
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2016 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
Zotero.Profile = {
|
||||
dir: OS.Constants.Path.profileDir,
|
||||
|
||||
getDefaultInProfilesDir: Zotero.Promise.coroutine(function* (profilesDir) {
|
||||
var profilesIni = OS.Path.join(profilesDir, "profiles.ini");
|
||||
|
||||
try {
|
||||
var iniContents = yield Zotero.File.getContentsAsync(profilesIni);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
// cheap and dirty ini parser
|
||||
var curSection = null;
|
||||
var defaultSection = null;
|
||||
var nSections = 0;
|
||||
for (let line of iniContents.split(/(?:\r?\n|\r)/)) {
|
||||
let tline = line.trim();
|
||||
if(tline[0] == "[" && tline[tline.length-1] == "]") {
|
||||
curSection = {};
|
||||
if(tline != "[General]") nSections++;
|
||||
} else if(curSection && tline != "") {
|
||||
let equalsIndex = tline.indexOf("=");
|
||||
let key = tline.substr(0, equalsIndex);
|
||||
let val = tline.substr(equalsIndex+1);
|
||||
curSection[key] = val;
|
||||
if(key == "Default" && val == "1") {
|
||||
defaultSection = curSection;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!defaultSection && curSection) defaultSection = curSection;
|
||||
|
||||
if (!defaultSection || !defaultSection.Path) return false;
|
||||
|
||||
var defaultProfile = defaultSection.IsRelative === "1"
|
||||
? OS.Path.join(profilesDir, ...defaultSection.Path.split("/"))
|
||||
: defaultSection.Path;
|
||||
|
||||
if (!(yield OS.File.exists(defaultProfile))) {
|
||||
return false;
|
||||
}
|
||||
return [defaultProfile, nSections > 1];
|
||||
}),
|
||||
|
||||
|
||||
getProfilesDir: function () {
|
||||
return OS.Path.dirname(this.dir);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get the path to the Profiles directory of the other app from this one (Firefox or Zotero),
|
||||
* which may or may not exist
|
||||
*
|
||||
* @return {String} - Path
|
||||
*/
|
||||
getOtherAppProfilesDir: function () {
|
||||
var dir = OS.Path.dirname(OS.Path.dirname(OS.Path.dirname(this.dir)));
|
||||
|
||||
if (Zotero.isStandalone) {
|
||||
if (Zotero.isWin) {
|
||||
dir = OS.Path.join(OS.Path.dirname(dir), "Mozilla", "Firefox");
|
||||
}
|
||||
else if (Zotero.isMac) {
|
||||
dir = OS.Path.join(dir, "Firefox");
|
||||
}
|
||||
else {
|
||||
dir = OS.Path.join(dir, ".mozilla", "firefox");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Zotero.isWin) {
|
||||
dir = OS.Path.join(OS.Path.dirname(dir), "Zotero", "Zotero");
|
||||
}
|
||||
else if (Zotero.isMac) {
|
||||
dir = OS.Path.join(dir, "Zotero");
|
||||
} else {
|
||||
dir = OS.Path.join(dir, ".zotero", "zotero");
|
||||
}
|
||||
}
|
||||
|
||||
return OS.Path.join(dir, "Profiles");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Find other profile directories (for this app or the other app) using the given data directory
|
||||
*
|
||||
* @return {String[]}
|
||||
*/
|
||||
findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir) {
|
||||
let otherAppProfiles = yield this._findOtherAppProfiles();
|
||||
let otherProfiles = (yield this._findOtherProfiles()).concat(otherAppProfiles);
|
||||
|
||||
// First get profiles pointing at this directory
|
||||
otherProfiles = yield Zotero.Promise.filter(otherProfiles, Zotero.Promise.coroutine(function* (dir) {
|
||||
let prefs = yield Zotero.File.getContentsAsync(OS.Path.join(dir, "prefs.js"));
|
||||
prefs = prefs.trim().split(/(?:\r\n|\r|\n)/);
|
||||
|
||||
return prefs.some(line => {
|
||||
return line.includes("extensions.zotero.useDataDir") && line.includes("true");
|
||||
}) && prefs.some(line => {
|
||||
return line.match(/extensions\.zotero\.(lastD|d)ataDir/) && line.includes(dataDir)
|
||||
});
|
||||
}));
|
||||
|
||||
// If the parent of the source directory is a profile directory from the other app, add that
|
||||
// to the list, which addresses the situation where the source directory is a custom
|
||||
// location for the current profile but is a default in the other app (meaning it wouldn't
|
||||
// be added above).
|
||||
let dataDirParent = OS.Path.dirname(dataDir);
|
||||
if (otherAppProfiles.includes(dataDirParent) && !otherProfiles.includes(dataDirParent)) {
|
||||
otherProfiles.push(dataDirParent);
|
||||
}
|
||||
|
||||
if (otherProfiles.length) {
|
||||
Zotero.debug("Found other profiles pointing to " + dataDir);
|
||||
Zotero.debug(otherProfiles);
|
||||
}
|
||||
else {
|
||||
Zotero.debug("No other profiles point to " + dataDir);
|
||||
}
|
||||
|
||||
return otherProfiles;
|
||||
}),
|
||||
|
||||
|
||||
updateProfileDataDirectory: Zotero.Promise.coroutine(function* (profileDir, oldDir, newDir) {
|
||||
let prefsFile = OS.Path.join(profileDir, "prefs.js");
|
||||
let prefsFileTmp = OS.Path.join(profileDir, "prefs.js.tmp");
|
||||
Zotero.debug("Updating " + prefsFile + " to point to new data directory");
|
||||
let contents = yield Zotero.File.getContentsAsync(prefsFile);
|
||||
contents = contents
|
||||
.trim()
|
||||
.split(/(?:\r\n|\r|\n)/)
|
||||
// Remove existing lines
|
||||
.filter(line => !line.match(/extensions\.zotero\.(useD|lastD|d)ataDir/));
|
||||
// Shouldn't happen, but let's make sure we don't corrupt the prefs file
|
||||
let safeVal = newDir.replace(/["]/g, "");
|
||||
contents.push(
|
||||
`user_pref("extensions.zotero.dataDir", "${safeVal}");`,
|
||||
`user_pref("extensions.zotero.lastDataDir", "${safeVal}");`,
|
||||
'user_pref("extensions.zotero.useDataDir", true);'
|
||||
);
|
||||
let lineSep = Zotero.isWin ? "\r\n" : "\n";
|
||||
contents = contents.join(lineSep) + lineSep;
|
||||
yield OS.File.writeAtomic(
|
||||
prefsFile,
|
||||
contents,
|
||||
{
|
||||
tmpPath: prefsFileTmp,
|
||||
encoding: 'utf-8'
|
||||
}
|
||||
);
|
||||
}),
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get all profile directories within the given directory
|
||||
*
|
||||
* @return {String[]} - Array of paths
|
||||
*/
|
||||
_getProfilesInDir: Zotero.Promise.coroutine(function* (profilesDir) {
|
||||
var dirs = [];
|
||||
yield Zotero.File.iterateDirectory(profilesDir, function* (iterator) {
|
||||
while (true) {
|
||||
let entry = yield iterator.next();
|
||||
if (entry.isDir && (yield OS.File.exists(OS.Path.join(entry.path, "prefs.js")))) {
|
||||
dirs.push(entry.path);
|
||||
}
|
||||
}
|
||||
});
|
||||
return dirs;
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Find other profile directories for this app (Firefox or Zotero)
|
||||
*
|
||||
* @return {String[]} - Array of paths
|
||||
*/
|
||||
_findOtherProfiles: Zotero.Promise.coroutine(function* () {
|
||||
var profileDir = this.dir;
|
||||
var profilesDir = this.getProfilesDir();
|
||||
return this._getProfilesInDir(profilesDir).filter(dir => dir != profileDir);
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Find profile directories for the other app (Firefox or Zotero)
|
||||
*
|
||||
* @return {String[]} - Array of paths
|
||||
*/
|
||||
_findOtherAppProfiles: Zotero.Promise.coroutine(function* () {
|
||||
var dir = this.getOtherAppProfilesDir();
|
||||
return (yield OS.File.exists(dir)) ? this._getProfilesInDir(dir) : [];
|
||||
})
|
||||
};
|
|
@ -138,7 +138,7 @@ Zotero.Sync.Data.Local = {
|
|||
accept = true;
|
||||
}
|
||||
// else if (io.extra1) {
|
||||
// if (Zotero.forceNewDataDirectory(win)) {
|
||||
// if (Zotero.DataDirectory.forceChange(win)) {
|
||||
// var ps = Services.prompt;
|
||||
// ps.alert(null,
|
||||
// Zotero.getString('general.restartRequired'),
|
||||
|
|
|
@ -34,9 +34,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
*/
|
||||
(function(){
|
||||
// Privileged (public) methods
|
||||
this.getProfileDirectory = getProfileDirectory;
|
||||
this.getStorageDirectory = getStorageDirectory;
|
||||
this.chooseZoteroDirectory = chooseZoteroDirectory;
|
||||
this.debug = debug;
|
||||
this.log = log;
|
||||
this.logError = logError;
|
||||
|
@ -103,7 +101,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
this.hiDPISuffix = "";
|
||||
|
||||
var _startupErrorHandler;
|
||||
var _dataDirectory = false;
|
||||
var _localizedStringBundle;
|
||||
|
||||
var _locked = false;
|
||||
|
@ -275,7 +272,8 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
_addToolbarIcon();
|
||||
|
||||
try {
|
||||
var dataDir = Zotero.getZoteroDirectory();
|
||||
yield Zotero.DataDirectory.init();
|
||||
var dataDir = Zotero.DataDirectory.dir;
|
||||
}
|
||||
catch (e) {
|
||||
// Zotero dir not found
|
||||
|
@ -301,11 +299,11 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
|
||||
// Revert to home directory
|
||||
if (index == 1) {
|
||||
Zotero.chooseZoteroDirectory(false, true);
|
||||
Zotero.DataDirectory.choose(false, true);
|
||||
}
|
||||
// Locate data directory
|
||||
else if (index == 2) {
|
||||
Zotero.chooseZoteroDirectory();
|
||||
Zotero.DataDirectory.choose();
|
||||
}
|
||||
}
|
||||
_addToolbarIcon();
|
||||
|
@ -318,14 +316,16 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
|
||||
if (!Zotero.isConnector) {
|
||||
yield Zotero.checkForDataDirectoryMigration(dataDir.path, this.getDefaultDataDir());
|
||||
yield Zotero.DataDirectory.checkForMigration(
|
||||
dataDir, Zotero.DataDirectory.defaultDir
|
||||
);
|
||||
if (this.skipLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure data directory isn't in Dropbox, etc.
|
||||
if (Zotero.isStandalone) {
|
||||
Zotero.checkForUnsafeDataDirectory(dataDir.path);
|
||||
yield Zotero.DataDirectory.checkForUnsafeLocation(dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -456,9 +456,9 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
Zotero.VersionHeader.init();
|
||||
|
||||
// Check for data reset/restore
|
||||
var dataDir = Zotero.getZoteroDirectory();
|
||||
var restoreFile = OS.Path.join(dataDir.path, 'restore-from-server');
|
||||
var resetDataDirFile = OS.Path.join(dataDir.path, 'reset-data-directory');
|
||||
var dataDir = Zotero.DataDirectory.dir;
|
||||
var restoreFile = OS.Path.join(dataDir, 'restore-from-server');
|
||||
var resetDataDirFile = OS.Path.join(dataDir, 'reset-data-directory');
|
||||
|
||||
var result = yield Zotero.Promise.all([OS.File.exists(restoreFile), OS.File.exists(resetDataDirFile)]);
|
||||
if (result.some(r => r)) {
|
||||
|
@ -478,7 +478,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
Zotero.restoreFromServer = true;
|
||||
} else if (Zotero.resetDataDir) {
|
||||
Zotero.initAutoSync = true;
|
||||
var storageDir = OS.Path.join(dataDir.path, 'storage');
|
||||
var storageDir = OS.Path.join(dataDir, 'storage');
|
||||
yield Zotero.Promise.all([
|
||||
OS.File.removeDir(storageDir, {ignoreAbsent: true}),
|
||||
OS.File.remove(resetDataDirFile)
|
||||
|
@ -509,9 +509,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
if (Zotero.isStandalone) {
|
||||
let dbSystemVersion = yield Zotero.Schema.getDBVersion('system');
|
||||
if (dbSystemVersion > 0 && dbSystemVersion < 31) {
|
||||
var dir = Zotero.getProfileDirectory();
|
||||
dir.append('zotero');
|
||||
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.createInstance(Components.interfaces.nsIPromptService);
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
|
@ -534,8 +531,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
|
||||
// Default location
|
||||
if (index == 0) {
|
||||
Zotero.File.createDirectoryIfMissing(dir);
|
||||
|
||||
Zotero.Prefs.set("useDataDir", false)
|
||||
|
||||
Services.startup.quit(
|
||||
|
@ -545,7 +540,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
// Select new data directory
|
||||
else if (index == 1) {
|
||||
var dir = Zotero.chooseZoteroDirectory(true);
|
||||
let dir = yield Zotero.DataDirectory.choose(true);
|
||||
if (!dir) {
|
||||
quit = true;
|
||||
}
|
||||
|
@ -889,284 +884,18 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
});
|
||||
|
||||
|
||||
function getProfileDirectory(){
|
||||
return Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
|
||||
}
|
||||
|
||||
function getDefaultProfile(prefDir) {
|
||||
// find profiles.ini file
|
||||
var profilesIni = prefDir.clone();
|
||||
profilesIni.append("profiles.ini");
|
||||
if(!profilesIni.exists()) return false;
|
||||
var iniContents = Zotero.File.getContents(profilesIni);
|
||||
|
||||
// cheap and dirty ini parser
|
||||
var curSection = null;
|
||||
var defaultSection = null;
|
||||
var nSections = 0;
|
||||
for (let line of iniContents.split(/(?:\r?\n|\r)/)) {
|
||||
let tline = line.trim();
|
||||
if(tline[0] == "[" && tline[tline.length-1] == "]") {
|
||||
curSection = {};
|
||||
if(tline != "[General]") nSections++;
|
||||
} else if(curSection && tline != "") {
|
||||
let equalsIndex = tline.indexOf("=");
|
||||
let key = tline.substr(0, equalsIndex);
|
||||
let val = tline.substr(equalsIndex+1);
|
||||
curSection[key] = val;
|
||||
if(key == "Default" && val == "1") {
|
||||
defaultSection = curSection;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!defaultSection && curSection) defaultSection = curSection;
|
||||
|
||||
// parse out ini to reveal profile
|
||||
if(!defaultSection || !defaultSection.Path) return false;
|
||||
|
||||
|
||||
if(defaultSection.IsRelative === "1") {
|
||||
var defaultProfile = prefDir.clone().QueryInterface(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
for each(var dir in defaultSection.Path.split("/")) defaultProfile.append(dir);
|
||||
} catch(e) {
|
||||
Zotero.logError("Could not find profile at "+defaultSection.Path);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
var defaultProfile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
defaultProfile.initWithPath(defaultSection.Path);
|
||||
}
|
||||
|
||||
if(!defaultProfile.exists()) return false;
|
||||
return [defaultProfile, nSections > 1];
|
||||
this.getProfileDirectory = function () {
|
||||
Zotero.warn("Zotero.getProfileDirectory() is deprecated -- use Zotero.Profile.dir");
|
||||
return Zotero.File.pathToFile(Zotero.Profile.dir);
|
||||
}
|
||||
|
||||
|
||||
this.getZoteroDirectory = function () {
|
||||
if (_dataDirectory != false) {
|
||||
return Zotero.File.pathToFile(_dataDirectory);
|
||||
}
|
||||
|
||||
var file;
|
||||
if (Zotero.Prefs.get('useDataDir')) {
|
||||
file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
|
||||
let prefVal = Zotero.Prefs.get('dataDir');
|
||||
// Convert old persistent descriptor pref to string path and clear obsolete lastDataDir pref
|
||||
//
|
||||
// persistentDescriptor now appears to return (and parse) a string path anyway on macOS,
|
||||
// which is the only place where it didn't use a string path to begin with, but be explicit
|
||||
// just in case there's some difference.
|
||||
//
|
||||
// A post-Mozilla prefs migration should do this same check, and then this conditional can
|
||||
// be removed.
|
||||
if (Zotero.Prefs.get('lastDataDir')) {
|
||||
try {
|
||||
file.persistentDescriptor = prefVal;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Persistent descriptor in extensions.zotero.dataDir did not resolve", 1);
|
||||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
throw (e);
|
||||
}
|
||||
// This removes lastDataDir
|
||||
this.setDataDirectory(file.path);
|
||||
}
|
||||
else {
|
||||
// If there's a migration marker in this directory and no database, migration was
|
||||
// interrupted before the database could be moved (or moving failed), so use the source
|
||||
// directory specified in the marker file.
|
||||
let migrationMarker = OS.Path.join(prefVal, this.DATA_DIR_MIGRATION_MARKER);
|
||||
let dbFile = OS.Path.join(prefVal, this.getDatabaseFilename());
|
||||
if (Zotero.File.pathToFile(migrationMarker).exists()
|
||||
&& !(Zotero.File.pathToFile(dbFile).exists())) {
|
||||
let contents = Zotero.File.getContents(migrationMarker);
|
||||
try {
|
||||
let { sourceDir } = JSON.parse(contents);
|
||||
file = Zotero.File.pathToFile(sourceDir);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(`Invalid marker file:\n\n${contents}`, 1);
|
||||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
file = Zotero.File.pathToFile(prefVal);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
Zotero.debug(`Invalid path '${prefVal}' in dataDir pref`, 1);
|
||||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.exists() && file.path != this.getDefaultDataDir()) {
|
||||
// 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.isLegacyDataDirectory(file.path)) {
|
||||
let newDefault = this.getDefaultDataDir();
|
||||
Zotero.debug(`Legacy data directory ${file.path} from pref not found `
|
||||
+ `-- reverting to ${newDefault}`, 1);
|
||||
file = Zotero.File.pathToFile(newDefault);
|
||||
this.setDataDirectory(newDefault);
|
||||
}
|
||||
// For other custom directories that don't exist, show not-found dialog
|
||||
else {
|
||||
Zotero.debug("Custom data directory ${file.path} not found", 1);
|
||||
throw { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
let dataDir = this.getDefaultDataDir();
|
||||
|
||||
//
|
||||
// TODO: asyncify
|
||||
//
|
||||
|
||||
// Check for ~/Zotero/zotero.sqlite
|
||||
dataDir = Zotero.File.pathToFile(dataDir);
|
||||
let dbFile = dataDir.clone();
|
||||
dbFile.append(this.getDatabaseFilename());
|
||||
if (dbFile.exists()) {
|
||||
Zotero.debug("Using data directory " + dataDir.path);
|
||||
this._cacheDataDirectory(dataDir.path);
|
||||
|
||||
// Set as a custom data directory so that 4.0 uses it
|
||||
this.setDataDirectory(dataDir.path);
|
||||
|
||||
return dataDir.clone();
|
||||
}
|
||||
|
||||
// Check for 'zotero' dir in profile dir
|
||||
let profileSubdir = Zotero.getProfileDirectory();
|
||||
profileSubdir.append('zotero');
|
||||
if (profileSubdir.exists()) {
|
||||
Zotero.debug("Using data directory " + profileSubdir.path);
|
||||
this._cacheDataDirectory(profileSubdir.path);
|
||||
return profileSubdir.clone();
|
||||
}
|
||||
|
||||
//
|
||||
// If Standalone and no directory yet, check Firefox directory, or vice versa
|
||||
//
|
||||
let prefDir = OS.Path.dirname(
|
||||
OS.Path.dirname(
|
||||
OS.Path.dirname(
|
||||
OS.Constants.Path.profileDir
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if(Zotero.isStandalone) {
|
||||
if (Zotero.isWin) {
|
||||
prefDir = OS.Path.join(OS.Path.dirname(prefDir), "Mozilla", "Firefox");
|
||||
}
|
||||
else if (Zotero.isMac) {
|
||||
prefDir = OS.Path.join(prefDir, "Firefox");
|
||||
}
|
||||
else {
|
||||
prefDir = OS.Path.join(prefDir, ".mozilla", "firefox");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Zotero.isWin) {
|
||||
prefDir = OS.Path.join(OS.Path.dirname(prefDir), "Zotero", "Zotero");
|
||||
}
|
||||
else if (Zotero.isMac) {
|
||||
prefDir = OS.Path.join(prefDir, "Zotero");
|
||||
} else {
|
||||
prefDir = OS.Path.join(prefDir, ".zotero", "zotero");
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Looking for existing profile in " + prefDir);
|
||||
|
||||
// get default profile
|
||||
var defProfile;
|
||||
try {
|
||||
defProfile = getDefaultProfile(Zotero.File.pathToFile(prefDir));
|
||||
} catch(e) {
|
||||
Zotero.debug("An error occurred locating the Firefox profile; not "+
|
||||
"attempting to migrate from Zotero for Firefox");
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
if(defProfile) {
|
||||
// get Zotero directory
|
||||
let profileDir = defProfile[0].path;
|
||||
Zotero.debug("Found default profile at " + profileDir);
|
||||
|
||||
// copy prefs
|
||||
let prefsFile = OS.Path.join(profileDir, "prefs.js");
|
||||
if (Zotero.File.pathToFile(prefsFile).exists()) {
|
||||
// build sandbox
|
||||
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
|
||||
Components.utils.evalInSandbox(
|
||||
"var prefs = {};"+
|
||||
"function user_pref(key, val) {"+
|
||||
"prefs[key] = val;"+
|
||||
"}"
|
||||
, sandbox);
|
||||
|
||||
// remove comments
|
||||
var prefsJs = Zotero.File.getContents(prefsFile);
|
||||
prefsJs = prefsJs.replace(/^#[^\r\n]*$/mg, "");
|
||||
|
||||
// evaluate
|
||||
Components.utils.evalInSandbox(prefsJs, sandbox);
|
||||
var prefs = sandbox.prefs;
|
||||
for(var key in prefs) {
|
||||
if(key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) === ZOTERO_CONFIG.PREF_BRANCH
|
||||
&& key !== "extensions.zotero.firstRun2") {
|
||||
Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// If data directory setting was transferred, use that
|
||||
if (Zotero.Prefs.get('useDataDir')) {
|
||||
return this.getZoteroDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a data directory in the default profile for the alternative app, use that
|
||||
let zoteroDir = OS.Path.join(profileDir, ZOTERO_CONFIG.ID);
|
||||
if (Zotero.File.pathToFile(zoteroDir).exists()) {
|
||||
this.setDataDirectory(zoteroDir);
|
||||
file = Zotero.File.pathToFile(zoteroDir);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
file = dataDir;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Using data directory " + file.path);
|
||||
Zotero.File.createDirectoryIfMissing(file);
|
||||
this._cacheDataDirectory(file.path);
|
||||
return file.clone();
|
||||
Zotero.warn("Zotero.getZoteroDirectory() is deprecated -- use Zotero.DataDirectory.dir");
|
||||
return Zotero.File.pathToFile(Zotero.DataDirectory.dir);
|
||||
}
|
||||
|
||||
|
||||
this.getDefaultDataDir = function () {
|
||||
// Use special data directory for tests
|
||||
if (Zotero.test) {
|
||||
return OS.Path.join(OS.Path.dirname(OS.Constants.Path.profileDir), "Zotero");
|
||||
}
|
||||
return OS.Path.join(OS.Constants.Path.homeDir, ZOTERO_CONFIG.CLIENT_NAME);
|
||||
};
|
||||
|
||||
|
||||
function getStorageDirectory(){
|
||||
var file = Zotero.getZoteroDirectory();
|
||||
|
||||
|
@ -1176,17 +905,9 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
|
||||
|
||||
this.getDatabaseFilename = function (name) {
|
||||
return (name || ZOTERO_CONFIG.ID) + '.sqlite';
|
||||
};
|
||||
|
||||
this.getZoteroDatabase = function (name, ext) {
|
||||
name = this.getDatabaseFilename(name);
|
||||
ext = ext ? '.' + ext : '';
|
||||
|
||||
var file = Zotero.getZoteroDirectory();
|
||||
file.append(name + ext);
|
||||
return file;
|
||||
Zotero.warn("Zotero.getZoteroDatabase() is deprecated -- use Zotero.DataDirectory.getDatabase()");
|
||||
return Zotero.File.pathToFile(Zotero.DataDirectory.getDatabase(name, ext));
|
||||
}
|
||||
|
||||
|
||||
|
@ -1229,582 +950,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
|
||||
|
||||
function chooseZoteroDirectory(forceQuitNow, useHomeDir, moreInfoCallback) {
|
||||
var win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
var ps = Services.prompt;
|
||||
|
||||
if (useHomeDir) {
|
||||
let changed = this.setDataDirectory(this.getDefaultDataDir());
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
while (true) {
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(win, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
|
||||
fp.displayDirectory = Zotero.getZoteroDirectory();
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() == nsIFilePicker.returnOK) {
|
||||
var file = fp.file;
|
||||
let dialogText = '';
|
||||
let dialogTitle = '';
|
||||
|
||||
if (file.path == (Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'))) {
|
||||
Zotero.debug("Data directory hasn't changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// In dropbox folder
|
||||
if (Zotero.File.isDropboxDirectory(file.path)) {
|
||||
dialogTitle = Zotero.getString('general.warning');
|
||||
dialogText = Zotero.getString('dataDir.unsafeLocation.selected.dropbox') + "\n\n"
|
||||
+ Zotero.getString('dataDir.unsafeLocation.selected.useAnyway');
|
||||
}
|
||||
else if (file.directoryEntries.hasMoreElements()) {
|
||||
let dbfile = file.clone();
|
||||
dbfile.append(this.getDatabaseFilename());
|
||||
|
||||
// Warn if non-empty and no zotero.sqlite
|
||||
if (!dbfile.exists()) {
|
||||
dialogTitle = Zotero.getString('dataDir.selectedDirNonEmpty.title');
|
||||
dialogText = Zotero.getString('dataDir.selectedDirNonEmpty.text');
|
||||
}
|
||||
}
|
||||
// Directory empty
|
||||
else {
|
||||
dialogTitle = Zotero.getString('dataDir.selectedDirEmpty.title');
|
||||
dialogText = Zotero.getString('dataDir.selectedDirEmpty.text', Zotero.appName) + '\n\n'
|
||||
+ Zotero.getString('dataDir.selectedDirEmpty.useNewDir');
|
||||
}
|
||||
// Warning dialog to be displayed
|
||||
if(dialogText !== '') {
|
||||
let buttonFlags = ps.STD_YES_NO_BUTTONS;
|
||||
if (moreInfoCallback) {
|
||||
buttonFlags += ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
|
||||
}
|
||||
let index = ps.confirmEx(null,
|
||||
dialogTitle,
|
||||
dialogText,
|
||||
buttonFlags,
|
||||
null,
|
||||
null,
|
||||
moreInfoCallback ? Zotero.getString('general.moreInformation') : null,
|
||||
null, {});
|
||||
|
||||
// Not OK -- return to file picker
|
||||
if (index == 1) {
|
||||
continue;
|
||||
}
|
||||
else if (index == 2) {
|
||||
setTimeout(function () {
|
||||
moreInfoCallback();
|
||||
}, 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.setDataDirectory(file.path);
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
if (!forceQuitNow) {
|
||||
buttonFlags += (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
}
|
||||
var app = Zotero.appName;
|
||||
var index = ps.confirmEx(null,
|
||||
Zotero.getString('general.restartRequired'),
|
||||
Zotero.getString('general.restartRequiredForChange', app)
|
||||
+ "\n\n" + Zotero.getString('dataDir.moveFilesToNewLocation', app),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.quitApp', app),
|
||||
forceQuitNow ? null : Zotero.getString('general.restartLater'),
|
||||
null, null, {});
|
||||
|
||||
if (forceQuitNow || index == 0) {
|
||||
Services.startup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
|
||||
return useHomeDir ? true : file;
|
||||
}
|
||||
|
||||
|
||||
this._cacheDataDirectory = function (dir) {
|
||||
_dataDirectory = dir;
|
||||
};
|
||||
|
||||
|
||||
this.forceNewDataDirectory = function(win) {
|
||||
if (!win) {
|
||||
win = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
}
|
||||
var ps = Services.prompt;
|
||||
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
while (true) {
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(win, Zotero.getString('dataDir.selectNewDir', Zotero.clientName), nsIFilePicker.modeGetFolder);
|
||||
fp.displayDirectory = Zotero.getZoteroDirectory();
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() == nsIFilePicker.returnOK) {
|
||||
var file = fp.file;
|
||||
|
||||
if (file.directoryEntries.hasMoreElements()) {
|
||||
ps.alert(null,
|
||||
Zotero.getString('dataDir.mustSelectEmpty.title'),
|
||||
Zotero.getString('dataDir.mustSelectEmpty.text')
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.setDataDirectory(file.path);
|
||||
|
||||
return file;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.warnOnUnsafeDataDir = true;
|
||||
this.checkForUnsafeDataDirectory = function (path) {
|
||||
if (this.warnOnUnsafeDataDir && Zotero.File.isDropboxDirectory(path)
|
||||
&& Zotero.Prefs.get('warnOnUnsafeDataDir')) {
|
||||
|
||||
this.warnOnUnsafeDataDir = false;
|
||||
let check = {value: false};
|
||||
let index = Services.prompt.confirmEx(
|
||||
null,
|
||||
Zotero.getString('general.warning'),
|
||||
Zotero.getString('dataDir.unsafeLocation.existing.dropbox') + "\n\n"
|
||||
+ Zotero.getString('dataDir.unsafeLocation.existing.chooseDifferent'),
|
||||
Services.prompt.STD_YES_NO_BUTTONS,
|
||||
null, null, null,
|
||||
Zotero.getString('general.dontShowWarningAgain'),
|
||||
check
|
||||
);
|
||||
|
||||
// Yes - display dialog.
|
||||
if (index == 0) {
|
||||
Zotero.chooseZoteroDirectory(true);
|
||||
}
|
||||
if (check.value) {
|
||||
Zotero.Prefs.set('warnOnUnsafeDataDir', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {Boolean} - True if the directory changed; false otherwise
|
||||
*/
|
||||
this.setDataDirectory = function (path) {
|
||||
var origPath = Zotero.Prefs.get('dataDir');
|
||||
|
||||
Zotero.Prefs.set('dataDir', path);
|
||||
// Clear legacy pref
|
||||
Zotero.Prefs.clear('lastDataDir');
|
||||
Zotero.Prefs.set('useDataDir', true);
|
||||
|
||||
return path != origPath;
|
||||
};
|
||||
|
||||
|
||||
this.isLegacyDataDirectory = function (dir) {
|
||||
// 'zotero'
|
||||
return OS.Path.basename(dir) == ZOTERO_CONFIG.ID
|
||||
// '69pmactz.default'
|
||||
&& OS.Path.basename(OS.Path.dirname(dir)).match(/^[0-9a-z]{8}\..+/)
|
||||
// 'Profiles'
|
||||
&& OS.Path.basename(OS.Path.dirname(OS.Path.dirname(dir))) == 'Profiles';
|
||||
}
|
||||
|
||||
|
||||
this.canMigrateDataDirectory = function () {
|
||||
// If (not default location) && (not useDataDir or within legacy location)
|
||||
var currentDir = this.getZoteroDirectory().path;
|
||||
if (currentDir == this.getDefaultDataDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Legacy default or set to legacy default from other program (Standalone/Z4Fx) to share data
|
||||
if (!Zotero.Prefs.get('useDataDir') || this.isLegacyDataDirectory(currentDir)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
this.revealDataDirectory = function () {
|
||||
return Zotero.File.reveal(this.getZoteroDirectory().path);
|
||||
},
|
||||
|
||||
|
||||
this.DATA_DIR_MIGRATION_MARKER = 'migrate-dir';
|
||||
|
||||
|
||||
this.markDataDirectoryForMigration = function (dir, automatic = false) {
|
||||
return Zotero.File.putContentsAsync(
|
||||
OS.Path.join(dir, this.DATA_DIR_MIGRATION_MARKER),
|
||||
JSON.stringify({
|
||||
sourceDir: dir,
|
||||
automatic
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Migrate data directory if necessary and show any errors
|
||||
*
|
||||
* @param {String} dataDir - Current directory
|
||||
* @param {String} targetDir - Target directory, which may be the same; except in tests, this is
|
||||
* the default data directory
|
||||
*/
|
||||
this.checkForDataDirectoryMigration = Zotero.Promise.coroutine(function* (dataDir, newDir) {
|
||||
if (!this.canMigrateDataDirectory(dataDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let migrationMarker = OS.Path.join(dataDir, this.DATA_DIR_MIGRATION_MARKER);
|
||||
try {
|
||||
var exists = yield OS.File.exists(migrationMarker)
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
let automatic = false;
|
||||
if (!exists) {
|
||||
// Migrate automatically on macOS and Linux -- this should match the check in
|
||||
// Zotero.File.moveDirectory()
|
||||
if (!Zotero.isWin && (yield OS.File.exists("/bin/mv"))) {
|
||||
automatic = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an existing pipe from other running versions of Zotero pointing at the same data
|
||||
// directory, and skip migration if found
|
||||
try {
|
||||
let foundPipe = yield Zotero.IPC.pipeExists();
|
||||
if (foundPipe) {
|
||||
Zotero.debug("Found existing pipe -- skipping migration");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Error checking for pipe -- skipping migration:\n\n" + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (automatic) {
|
||||
yield this.markDataDirectoryForMigration(dataDir, true);
|
||||
}
|
||||
|
||||
let sourceDir;
|
||||
let oldDir;
|
||||
let partial = false;
|
||||
|
||||
// Check whether this is an automatic or manual migration
|
||||
let contents;
|
||||
try {
|
||||
contents = yield Zotero.File.getContentsAsync(migrationMarker);
|
||||
({ sourceDir, automatic } = JSON.parse(contents));
|
||||
}
|
||||
catch (e) {
|
||||
if (contents !== undefined) {
|
||||
Zotero.debug(contents, 1);
|
||||
}
|
||||
Zotero.logError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not set to the default directory, so use current as old directory
|
||||
if (dataDir != newDir) {
|
||||
oldDir = dataDir;
|
||||
}
|
||||
// Unfinished migration -- already using new directory, so get path to previous
|
||||
// directory from the migration marker
|
||||
else {
|
||||
oldDir = sourceDir;
|
||||
partial = true;
|
||||
}
|
||||
|
||||
// Not yet used
|
||||
let progressHandler = function (progress, progressMax) {
|
||||
this.updateZoteroPaneProgressMeter(Math.round(progress / progressMax));
|
||||
}.bind(this);
|
||||
|
||||
let errors;
|
||||
let mode = automatic ? 'automatic' : 'manual';
|
||||
// This can seemingly fail due to a race condition building the Standalone window,
|
||||
// so just ignore it if it does
|
||||
try {
|
||||
this.showZoteroPaneProgressMeter(Zotero.getString("dataDir.migration.inProgress"));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
try {
|
||||
errors = yield Zotero.migrateDataDirectory(oldDir, newDir, partial, progressHandler);
|
||||
}
|
||||
catch (e) {
|
||||
// Complete failure (failed to create new directory, copy marker, or move database)
|
||||
Zotero.debug("Migration failed", 1);
|
||||
Zotero.logError(e);
|
||||
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
let index = ps.confirmEx(null,
|
||||
Zotero.getString('dataDir.migration.failure.title'),
|
||||
Zotero.getString(`dataDir.migration.failure.full.${mode}.text1`, ZOTERO_CONFIG.CLIENT_NAME)
|
||||
+ "\n\n"
|
||||
+ e
|
||||
+ "\n\n"
|
||||
+ Zotero.getString(`dataDir.migration.failure.full.${mode}.text2`, Zotero.appName)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.full.current', oldDir)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.full.recommended', newDir),
|
||||
buttonFlags,
|
||||
Zotero.getString('dataDir.migration.failure.full.showCurrentDirectoryAndQuit', Zotero.appName),
|
||||
Zotero.getString('general.notNow'),
|
||||
null, null, {}
|
||||
);
|
||||
if (index == 0) {
|
||||
yield Zotero.File.reveal(oldDir);
|
||||
this.skipLoading = true;
|
||||
Zotero.Utilities.Internal.quitZotero();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set data directory again
|
||||
Zotero.debug("Using new data directory " + newDir);
|
||||
this._cacheDataDirectory(newDir);
|
||||
// Tell Zotero for Firefox in connector mode to reload and find the new data directory
|
||||
if (this.isStandalone) {
|
||||
Zotero.IPC.broadcast('reinit');
|
||||
}
|
||||
|
||||
// At least the database was copied, but other things failed
|
||||
if (errors.length) {
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
let index = ps.confirmEx(null,
|
||||
Zotero.getString('dataDir.migration.failure.title'),
|
||||
Zotero.getString(`dataDir.migration.failure.partial.${mode}.text`,
|
||||
[ZOTERO_CONFIG.CLIENT_NAME, Zotero.appName])
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.partial.old', oldDir)
|
||||
+ "\n\n"
|
||||
+ Zotero.getString('dataDir.migration.failure.partial.new', newDir),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.tryAgain'),
|
||||
Zotero.getString('general.tryLater'),
|
||||
Zotero.getString('dataDir.migration.failure.partial.showDirectoriesAndQuit', Zotero.appName),
|
||||
null, {}
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
return this.checkForDataDirectoryMigration(newDir, newDir);
|
||||
}
|
||||
// Focus the first file/folder in the old directory
|
||||
else if (index == 2) {
|
||||
try {
|
||||
let it = new OS.File.DirectoryIterator(oldDir);
|
||||
let entry;
|
||||
try {
|
||||
entry = yield it.next();
|
||||
}
|
||||
catch (e) {
|
||||
if (e != StopIteration) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
it.close();
|
||||
}
|
||||
if (entry) {
|
||||
yield Zotero.File.reveal(entry.path);
|
||||
}
|
||||
// Focus the database file in the new directory
|
||||
yield Zotero.File.reveal(OS.Path.join(newDir, this.getDatabaseFilename()));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
Zotero.skipLoading = true;
|
||||
Zotero.Utilities.Internal.quitZotero();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Recursively moves data directory from one location to another
|
||||
* and updates the data directory setting
|
||||
*
|
||||
* If moving the database file fails, an error is thrown.
|
||||
* Otherwise, an array of errors is returned.
|
||||
*
|
||||
* @param {String} oldDir
|
||||
* @param {String} newDir
|
||||
* @return {Error[]}
|
||||
*/
|
||||
this.migrateDataDirectory = Zotero.Promise.coroutine(function* (oldDir, newDir, partial) {
|
||||
var dbName = this.getDatabaseFilename();
|
||||
var errors = [];
|
||||
|
||||
function addError(e) {
|
||||
errors.push(e);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
if (!(yield OS.File.exists(oldDir))) {
|
||||
Zotero.debug(`Old directory ${oldDir} doesn't exist -- nothing to migrate`);
|
||||
try {
|
||||
let newMigrationMarker = OS.Path.join(newDir, Zotero.DATA_DIR_MIGRATION_MARKER);
|
||||
Zotero.debug("Removing " + newMigrationMarker);
|
||||
yield OS.File.remove(newMigrationMarker);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
if (partial) {
|
||||
Zotero.debug(`Continuing data directory migration from ${oldDir} to ${newDir}`);
|
||||
}
|
||||
else {
|
||||
Zotero.debug(`Migrating data directory from ${oldDir} to ${newDir}`);
|
||||
}
|
||||
|
||||
// Create the new directory
|
||||
if (!partial) {
|
||||
try {
|
||||
yield OS.File.makeDir(
|
||||
newDir,
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
// If default dir exists and is non-empty, move it out of the way
|
||||
// ("Zotero-1", "Zotero-2", …)
|
||||
if (e instanceof OS.File.Error && e.becauseExists) {
|
||||
if (!(yield Zotero.File.directoryIsEmpty(newDir))) {
|
||||
let i = 1;
|
||||
while (true) {
|
||||
let backupDir = newDir + "-" + i++;
|
||||
if (yield OS.File.exists(backupDir)) {
|
||||
if (i > 5) {
|
||||
throw new Error("Too many backup directories "
|
||||
+ "-- stopped at " + backupDir);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Zotero.debug(`Moving existing directory to ${backupDir}`);
|
||||
yield Zotero.File.moveDirectory(newDir, backupDir);
|
||||
break;
|
||||
}
|
||||
yield OS.File.makeDir(
|
||||
newDir,
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy marker
|
||||
let oldMarkerFile = OS.Path.join(oldDir, this.DATA_DIR_MIGRATION_MARKER);
|
||||
// Marker won't exist on subsequent attempts after partial failure
|
||||
if (yield OS.File.exists(oldMarkerFile)) {
|
||||
yield OS.File.copy(oldMarkerFile, OS.Path.join(newDir, this.DATA_DIR_MIGRATION_MARKER));
|
||||
}
|
||||
|
||||
// Update the data directory setting first. If moving the database fails, getZoteroDirectory()
|
||||
// will continue to use the old directory based on the migration marker
|
||||
Zotero.setDataDirectory(newDir);
|
||||
|
||||
// Move database
|
||||
if (!partial) {
|
||||
Zotero.debug("Moving " + dbName);
|
||||
yield OS.File.move(OS.Path.join(oldDir, dbName), OS.Path.join(newDir, dbName));
|
||||
}
|
||||
|
||||
// Once the database has been moved, we can clear the migration marker from the old directory.
|
||||
// If the migration is interrupted after this, it can be continued later based on the migration
|
||||
// marker in the new directory.
|
||||
try {
|
||||
yield OS.File.remove(OS.Path.join(oldDir, this.DATA_DIR_MIGRATION_MARKER));
|
||||
}
|
||||
catch (e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
errors = errors.concat(yield Zotero.File.moveDirectory(
|
||||
oldDir,
|
||||
newDir,
|
||||
{
|
||||
allowExistingTarget: true,
|
||||
// Don't overwrite root files (except for hidden files like .DS_Store)
|
||||
noOverwrite: path => {
|
||||
return OS.Path.dirname(path) == oldDir && !OS.Path.basename(path).startsWith('.')
|
||||
},
|
||||
}
|
||||
));
|
||||
|
||||
if (errors.length) {
|
||||
Zotero.logError("Not all files were transferred from " + oldDir + " to " + newDir);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
let newMigrationMarker = OS.Path.join(newDir, Zotero.DATA_DIR_MIGRATION_MARKER);
|
||||
Zotero.debug("Removing " + newMigrationMarker);
|
||||
yield OS.File.remove(newMigrationMarker);
|
||||
|
||||
Zotero.debug("Migration successful");
|
||||
}
|
||||
catch (e) {
|
||||
addError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Launch a file, the best way we can
|
||||
*/
|
||||
|
|
|
@ -33,6 +33,7 @@ const Ci = Components.interfaces;
|
|||
/** XPCOM files to be loaded for all modes **/
|
||||
const xpcomFilesAll = [
|
||||
'zotero',
|
||||
'dataDirectory',
|
||||
'date',
|
||||
'debug',
|
||||
'error',
|
||||
|
@ -41,6 +42,7 @@ const xpcomFilesAll = [
|
|||
'mimeTypeHandler',
|
||||
'openurl',
|
||||
'ipc',
|
||||
'profile',
|
||||
'progressWindow',
|
||||
'translation/translate',
|
||||
'translation/translate_firefox',
|
||||
|
|
|
@ -474,7 +474,6 @@ function getTestDataUrl(path) {
|
|||
|
||||
/**
|
||||
* Returns an absolute path to an empty temporary directory
|
||||
* (i.e., test/tests/data)
|
||||
*/
|
||||
var getTempDirectory = Zotero.Promise.coroutine(function* getTempDirectory() {
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
describe("Zotero Core Functions", function () {
|
||||
describe("Zotero.DataDirectory", function () {
|
||||
var tmpDir, oldDir, newDir, dbFilename, oldDBFile, newDBFile, oldStorageDir, newStorageDir,
|
||||
oldTranslatorsDir, newTranslatorsDir, translatorName1, translatorName2,
|
||||
oldStorageDir1, newStorageDir1, storageFile1, oldStorageDir2, newStorageDir2, storageFile2,
|
||||
|
@ -12,7 +12,7 @@ describe("Zotero Core Functions", function () {
|
|||
tmpDir = yield getTempDirectory();
|
||||
oldDir = OS.Path.join(tmpDir, "old");
|
||||
newDir = OS.Path.join(tmpDir, "new");
|
||||
dbFilename = Zotero.getDatabaseFilename();
|
||||
dbFilename = Zotero.DataDirectory.getDatabaseFilename();
|
||||
oldDBFile = OS.Path.join(oldDir, dbFilename);
|
||||
newDBFile = OS.Path.join(newDir, dbFilename);
|
||||
oldStorageDir = OS.Path.join(oldDir, "storage");
|
||||
|
@ -33,24 +33,23 @@ describe("Zotero Core Functions", function () {
|
|||
str4 = '4';
|
||||
str5 = '5';
|
||||
str6 = '6';
|
||||
oldMigrationMarker = OS.Path.join(oldDir, Zotero.DATA_DIR_MIGRATION_MARKER);
|
||||
newMigrationMarker = OS.Path.join(newDir, Zotero.DATA_DIR_MIGRATION_MARKER);
|
||||
oldMigrationMarker = OS.Path.join(oldDir, Zotero.DataDirectory.MIGRATION_MARKER);
|
||||
newMigrationMarker = OS.Path.join(newDir, Zotero.DataDirectory.MIGRATION_MARKER);
|
||||
|
||||
stubs.canMigrate = sinon.stub(Zotero, "canMigrateDataDirectory").returns(true);
|
||||
stubs.canMigrate = sinon.stub(Zotero.DataDirectory, "canMigrate").returns(true);
|
||||
// A pipe always exists during tests, since Zotero is running
|
||||
stubs.pipeExists = sinon.stub(Zotero.IPC, "pipeExists").returns(Zotero.Promise.resolve(false));
|
||||
});
|
||||
|
||||
beforeEach(function* () {
|
||||
// Trigger a call to setDataDirectory() now to avoid affecting the stub call count
|
||||
Zotero.getZoteroDirectory();
|
||||
stubs.setDataDir = sinon.stub(Zotero, "setDataDirectory");
|
||||
stubs.setDataDir = sinon.stub(Zotero.DataDirectory, "set");
|
||||
});
|
||||
|
||||
afterEach(function* () {
|
||||
yield removeDir(oldDir);
|
||||
yield removeDir(newDir);
|
||||
Zotero._cacheDataDirectory(false);
|
||||
Zotero.DataDirectory._cache(false);
|
||||
yield Zotero.DataDirectory.init();
|
||||
|
||||
stubs.setDataDir.restore();
|
||||
});
|
||||
|
@ -83,7 +82,7 @@ describe("Zotero Core Functions", function () {
|
|||
let storageDir1 = OS.Path.join(storageDir, 'AAAAAAAA');
|
||||
let storageDir2 = OS.Path.join(storageDir, 'BBBBBBBB');
|
||||
let translatorsDir = OS.Path.join(dir, 'translators');
|
||||
let migrationMarker = OS.Path.join(dir, Zotero.DATA_DIR_MIGRATION_MARKER);
|
||||
let migrationMarker = OS.Path.join(dir, Zotero.DataDirectory.MIGRATION_MARKER);
|
||||
|
||||
// Database
|
||||
yield Zotero.File.putContentsAsync(OS.Path.join(dir, dbFilename), str1);
|
||||
|
@ -141,7 +140,7 @@ describe("Zotero Core Functions", function () {
|
|||
});
|
||||
|
||||
|
||||
describe("#checkForDataDirectoryMigration()", function () {
|
||||
describe("#checkForMigration()", function () {
|
||||
let fileMoveStub;
|
||||
|
||||
before(function () {
|
||||
|
@ -198,7 +197,7 @@ describe("Zotero Core Functions", function () {
|
|||
)
|
||||
);
|
||||
});
|
||||
yield Zotero.checkForDataDirectoryMigration(oldDir, newDir);
|
||||
yield Zotero.DataDirectory.checkForMigration(oldDir, newDir);
|
||||
yield promise;
|
||||
yield promise2;
|
||||
|
||||
|
@ -240,7 +239,7 @@ describe("Zotero Core Functions", function () {
|
|||
)
|
||||
);
|
||||
});
|
||||
yield Zotero.checkForDataDirectoryMigration(oldDir, newDir);
|
||||
yield Zotero.DataDirectory.checkForMigration(oldDir, newDir);
|
||||
yield promise;
|
||||
|
||||
assert.isTrue(stub2.calledOnce);
|
||||
|
@ -267,7 +266,7 @@ describe("Zotero Core Functions", function () {
|
|||
|
||||
it("should remove marker if old directory doesn't exist", function* () {
|
||||
yield populateDataDirectory(newDir, oldDir);
|
||||
yield Zotero.checkForDataDirectoryMigration(newDir, newDir);
|
||||
yield Zotero.DataDirectory.checkForMigration(newDir, newDir);
|
||||
yield checkMigration({
|
||||
skipSetDataDirectory: true
|
||||
});
|
||||
|
@ -275,7 +274,7 @@ describe("Zotero Core Functions", function () {
|
|||
});
|
||||
|
||||
|
||||
describe("#migrateDataDirectory()", function () {
|
||||
describe("#migrate()", function () {
|
||||
// Define tests and store for running in non-mv mode
|
||||
var tests = [];
|
||||
function add(desc, fn) {
|
||||
|
@ -285,7 +284,7 @@ describe("Zotero Core Functions", function () {
|
|||
|
||||
add("should move all files and folders", function* () {
|
||||
yield populateDataDirectory(oldDir);
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir);
|
||||
yield checkMigration();
|
||||
});
|
||||
|
||||
|
@ -295,7 +294,7 @@ describe("Zotero Core Functions", function () {
|
|||
|
||||
yield OS.File.copy(oldMigrationMarker, newMigrationMarker);
|
||||
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir, true);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
|
||||
yield checkMigration();
|
||||
});
|
||||
|
||||
|
@ -306,7 +305,7 @@ describe("Zotero Core Functions", function () {
|
|||
yield OS.File.copy(oldMigrationMarker, newMigrationMarker);
|
||||
yield OS.File.move(OS.Path.join(oldDir, dbFilename), OS.Path.join(newDir, dbFilename));
|
||||
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir, true);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
|
||||
yield checkMigration();
|
||||
});
|
||||
|
||||
|
@ -322,7 +321,7 @@ describe("Zotero Core Functions", function () {
|
|||
yield removeDir(newTranslatorsDir);
|
||||
yield removeDir(newStorageDir2);
|
||||
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir, true);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
|
||||
yield checkMigration();
|
||||
});
|
||||
|
||||
|
@ -331,7 +330,7 @@ describe("Zotero Core Functions", function () {
|
|||
yield OS.File.makeDir(newDir, { unixMode: 0o755 });
|
||||
yield Zotero.File.putContentsAsync(OS.Path.join(newDir, 'existing'), '');
|
||||
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir);
|
||||
yield checkMigration();
|
||||
|
||||
assert.isTrue(yield OS.File.exists(OS.Path.join(newDir + "-1", 'existing')));
|
||||
|
@ -374,7 +373,7 @@ describe("Zotero Core Functions", function () {
|
|||
}
|
||||
});
|
||||
|
||||
yield Zotero.migrateDataDirectory(oldDir, newDir);
|
||||
yield Zotero.DataDirectory.migrate(oldDir, newDir);
|
||||
|
||||
stub1.restore();
|
||||
|
190
test/tests/profileTest.js
Normal file
190
test/tests/profileTest.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
"use strict";
|
||||
|
||||
describe("Zotero.Profile", function () {
|
||||
var tmpDir;
|
||||
var profile1 = "ht79g2qb.Test1";
|
||||
var profile2 = "b9auumgf.Test2";
|
||||
var profile3 = "7sgqhns3.Test3";
|
||||
|
||||
beforeEach(function* () {
|
||||
tmpDir = yield getTempDirectory();
|
||||
var contents = `[General]
|
||||
StartWithLastProfile=0
|
||||
|
||||
[Profile0]
|
||||
Name=Test 1
|
||||
IsRelative=1
|
||||
Path=Profiles/${profile1}
|
||||
Default=1
|
||||
|
||||
[Profile1]
|
||||
Name=Test 2
|
||||
IsRelative=1
|
||||
Path=Profiles/${profile2}
|
||||
|
||||
[Profile1]
|
||||
Name=Test 3
|
||||
IsRelative=1
|
||||
Path=Profiles/${profile3}
|
||||
`;
|
||||
yield Zotero.File.putContentsAsync(OS.Path.join(tmpDir, "profiles.ini"), contents);
|
||||
yield OS.File.makeDir(
|
||||
OS.Path.join(tmpDir, "Profiles", profile1),
|
||||
{
|
||||
unixMode: 0o755,
|
||||
from: tmpDir
|
||||
}
|
||||
);
|
||||
yield OS.File.makeDir(OS.Path.join(tmpDir, "Profiles", profile2), { unixMode: 0o755 });
|
||||
yield OS.File.makeDir(OS.Path.join(tmpDir, "Profiles", profile3), { unixMode: 0o755 });
|
||||
});
|
||||
|
||||
|
||||
describe("#getDefaultInProfilesDir()", function () {
|
||||
it("should parse a profiles.ini file", function* () {
|
||||
var [dir, multiple] = yield Zotero.Profile.getDefaultInProfilesDir(tmpDir);
|
||||
assert.equal(dir, OS.Path.join(tmpDir, "Profiles", profile1));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("#findOtherProfilesUsingDataDirectory()", function () {
|
||||
it("should find profile with directory as a custom location", function* () {
|
||||
let dataDir = Zotero.DataDirectory.dir;
|
||||
let contents1 = `user_pref("extensions.lastAppVersion", "49.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
let contents2 = `user_pref("extensions.lastAppVersion", "50.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.zotero.dataDir", "${dataDir}");
|
||||
user_pref("extensions.zotero.useDataDir", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
let otherDir = OS.Path.join(OS.Path.dirname(dataDir), "Other");
|
||||
let contents3 = `user_pref("extensions.lastAppVersion", "51.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.zotero.dataDir", "${otherDir}");
|
||||
user_pref("extensions.zotero.useDataDir", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
|
||||
let prefsFile1 = OS.Path.join(tmpDir, "Profiles", profile1, "prefs.js");
|
||||
let prefsFile2 = OS.Path.join(tmpDir, "Profiles", profile2, "prefs.js");
|
||||
let prefsFile3 = OS.Path.join(tmpDir, "Profiles", profile3, "prefs.js");
|
||||
yield Zotero.File.putContentsAsync(prefsFile1, contents1);
|
||||
yield Zotero.File.putContentsAsync(prefsFile2, contents2);
|
||||
yield Zotero.File.putContentsAsync(prefsFile3, contents3);
|
||||
|
||||
var stub = sinon.stub(Zotero.Profile, "getOtherAppProfilesDir")
|
||||
.returns(OS.Path.join(tmpDir, "Profiles"));
|
||||
|
||||
var dirs = yield Zotero.Profile.findOtherProfilesUsingDataDirectory(dataDir);
|
||||
|
||||
stub.restore();
|
||||
|
||||
assert.sameMembers(dirs, [OS.Path.join(tmpDir, "Profiles", profile2)]);
|
||||
assert.lengthOf(dirs, 1);
|
||||
});
|
||||
|
||||
|
||||
it("should find other-app profile with directory as a legacy default location", function* () {
|
||||
let contents1 = `user_pref("extensions.lastAppVersion", "49.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
let contents2 = `user_pref("extensions.lastAppVersion", "50.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
|
||||
let prefsFile1 = OS.Path.join(tmpDir, "Profiles", profile1, "prefs.js");
|
||||
let prefsFile2 = OS.Path.join(tmpDir, "Profiles", profile2, "prefs.js");
|
||||
yield Zotero.File.putContentsAsync(prefsFile1, contents1);
|
||||
yield Zotero.File.putContentsAsync(prefsFile2, contents2);
|
||||
|
||||
var stub = sinon.stub(Zotero.Profile, "getOtherAppProfilesDir")
|
||||
.returns(OS.Path.join(tmpDir, "Profiles"));
|
||||
|
||||
Components.utils.import("resource://zotero/config.js");
|
||||
var dirs = yield Zotero.Profile.findOtherProfilesUsingDataDirectory(
|
||||
OS.Path.join(OS.Path.dirname(prefsFile1), Zotero.DataDirectory.legacyDirName)
|
||||
);
|
||||
|
||||
stub.restore();
|
||||
|
||||
assert.sameMembers(dirs, [OS.Path.join(tmpDir, "Profiles", profile1)]);
|
||||
assert.lengthOf(dirs, 1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("#updateProfileDataDirectory()", function () {
|
||||
it("should add new lines to prefs.js", function* () {
|
||||
let prefsFile = OS.Path.join(tmpDir, "Profiles", profile1, "prefs.js");
|
||||
let oldDir = OS.Path.join(OS.Path.dirname(tmpDir), "Old", "Zotero");
|
||||
let newDir = OS.Path.join(OS.Path.dirname(tmpDir), "New", "Zotero");
|
||||
|
||||
let contents = `user_pref("extensions.lastAppVersion", "50.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
let addition = `user_pref("extensions.zotero.dataDir", "${newDir}");
|
||||
user_pref("extensions.zotero.lastDataDir", "${newDir}");
|
||||
user_pref("extensions.zotero.useDataDir", true);
|
||||
`;
|
||||
|
||||
yield Zotero.File.putContentsAsync(prefsFile, contents);
|
||||
yield Zotero.Profile.updateProfileDataDirectory(
|
||||
OS.Path.join(tmpDir, "Profiles", profile1),
|
||||
oldDir,
|
||||
newDir
|
||||
);
|
||||
|
||||
let newContents = yield Zotero.File.getContentsAsync(prefsFile);
|
||||
|
||||
assert.equal(newContents, contents + addition);
|
||||
});
|
||||
|
||||
|
||||
it("should replace existing lines in prefs.js", function* () {
|
||||
let prefsFile = OS.Path.join(tmpDir, "Profiles", profile1, "prefs.js");
|
||||
let oldDir = OS.Path.join(OS.Path.dirname(tmpDir), "Old", "Zotero");
|
||||
let newDir = OS.Path.join(OS.Path.dirname(tmpDir), "New", "Zotero");
|
||||
|
||||
let contents = `user_pref("extensions.lastAppVersion", "50.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.zotero.dataDir", "old-mac-persistent-descriptor");
|
||||
user_pref("extensions.zotero.lastDataDir", "${oldDir}");
|
||||
user_pref("extensions.zotero.useDataDir", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
`;
|
||||
let expectedContents = `user_pref("extensions.lastAppVersion", "50.0");
|
||||
user_pref("extensions.shownSelectionUI", true);
|
||||
user_pref("extensions.ui.locale.hidden", true);
|
||||
user_pref("loop.copy.ticket", 196);
|
||||
user_pref("extensions.zotero.dataDir", "${newDir}");
|
||||
user_pref("extensions.zotero.lastDataDir", "${newDir}");
|
||||
user_pref("extensions.zotero.useDataDir", true);
|
||||
`;
|
||||
|
||||
yield Zotero.File.putContentsAsync(prefsFile, contents);
|
||||
yield Zotero.Profile.updateProfileDataDirectory(
|
||||
OS.Path.join(tmpDir, "Profiles", profile1),
|
||||
oldDir,
|
||||
newDir
|
||||
);
|
||||
|
||||
let newContents = yield Zotero.File.getContentsAsync(prefsFile);
|
||||
|
||||
assert.equal(newContents, expectedContents);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -79,7 +79,7 @@ describe("Zotero.Sync.Data.Local", function() {
|
|||
|
||||
// extra1 functionality not used at the moment
|
||||
it.skip("should prompt for data reset and allow to choose a new data directory", function* (){
|
||||
sinon.stub(Zotero, 'forceNewDataDirectory').returns(true);
|
||||
sinon.stub(Zotero.DataDirectory, 'forceChange').returns(true);
|
||||
yield Zotero.Users.setCurrentUserID(1);
|
||||
yield Zotero.Users.setCurrentUsername("A");
|
||||
|
||||
|
@ -88,10 +88,10 @@ describe("Zotero.Sync.Data.Local", function() {
|
|||
var cont = yield Zotero.Sync.Data.Local.checkUser(window, 2, "B");
|
||||
var resetDataDirFileExists = yield OS.File.exists(resetDataDirFile);
|
||||
assert.isTrue(cont);
|
||||
assert.isTrue(Zotero.forceNewDataDirectory.called);
|
||||
assert.isTrue(Zotero.DataDirectory.forceChange.called);
|
||||
assert.isFalse(resetDataDirFileExists);
|
||||
|
||||
Zotero.forceNewDataDirectory.restore();
|
||||
Zotero.DataDirectory.forceChange.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue