From c6ab18634de37aa829f479c9fc98af3726c113fc Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Mon, 31 Jul 2017 05:40:34 -0400 Subject: [PATCH] Improve data directory startup error handling Better instructions and behavior if the data directory is inaccessible or missing, including automatically detecting a directory at the default location if the configured directory is missing and offering to use the default instead. Together, this means that if, say, security software prevents Zotero from accessing the data directory within the Firefox profile, it will suggest that the user move it to ~/Zotero and then prompt to use that directory. --- chrome/content/zotero/xpcom/zotero.js | 145 ++++++++++++++----- chrome/locale/en-US/zotero/zotero.properties | 10 +- 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 02c321647b..3b695bdce0 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -292,33 +292,82 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); catch (e) { // Zotero dir not found if (e.name == 'NS_ERROR_FILE_NOT_FOUND') { - Zotero.startupError = Zotero.getString('dataDir.notFound'); + let foundInDefault = false; + try { + foundInDefault = (yield OS.File.exists(Zotero.DataDirectory.defaultDir)) + && (yield OS.File.exists( + OS.Path.join( + Zotero.DataDirectory.defaultDir, + Zotero.DataDirectory.getDatabaseFilename() + ) + )); + } + catch (e) { + Zotero.logError(e); + } + + let previousDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'); + Zotero.startupError = foundInDefault + ? Zotero.getString( + 'dataDir.notFound.defaultFound', + [ + Zotero.clientName, + previousDir, + Zotero.DataDirectory.defaultDir + ] + ) + : Zotero.getString('dataDir.notFound', Zotero.clientName); _startupErrorHandler = function() { 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) - + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING) - + (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING); + var 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; // TEMP: lastDataDir can be removed once old persistent descriptors have been // converted, which they are in getZoteroDirectory() in 5.0 - var previousDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'); - var index = ps.confirmEx(null, - Zotero.getString('general.error'), - Zotero.startupError + '\n\n' + - Zotero.getString('dataDir.previousDir') + ' ' + previousDir, - buttonFlags, - Zotero.getString('general.quit'), - Zotero.getString('dataDir.useDefaultLocation'), - Zotero.getString('general.locate'), - null, {}); - - // Revert to home directory - if (index == 1) { - Zotero.DataDirectory.choose(true, true); + if (foundInDefault) { + let index = ps.confirmEx(null, + Zotero.getString('general.error'), + Zotero.startupError, + buttonFlags, + Zotero.getString('dataDir.useNewLocation'), + Zotero.getString('general.quit'), + Zotero.getString('general.locate'), + null, {} + ); + // Revert to home directory + if (index == 0) { + Zotero.DataDirectory.set(Zotero.DataDirectory.defaultDir); + Zotero.Utilities.Internal.quit(true); + return; + } + // Locate data directory + else if (index == 2) { + Zotero.DataDirectory.choose(true); + } + } - // Locate data directory - else if (index == 2) { - Zotero.DataDirectory.choose(true); + else { + let index = ps.confirmEx(null, + Zotero.getString('general.error'), + Zotero.startupError + '\n\n' + + Zotero.getString('dataDir.previousDir') + ' ' + previousDir, + buttonFlags, + Zotero.getString('general.quit'), + Zotero.getString('dataDir.useDefaultLocation'), + Zotero.getString('general.locate'), + null, {} + ); + // Revert to home directory + if (index == 1) { + Zotero.DataDirectory.set(Zotero.DataDirectory.defaultDir); + Zotero.Utilities.Internal.quit(true); + return; + } + // Locate data directory + else if (index == 2) { + Zotero.DataDirectory.choose(true); + } } } return; @@ -351,13 +400,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); Zotero.IPC.init(); } catch (e) { - if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') { - var msg = Zotero.localeJoin([ - Zotero.getString('startupError.databaseCannotBeOpened'), - Zotero.getString('startupError.checkPermissions') - ]); - Zotero.startupError = msg; - Zotero.logError(e); + if (_checkDataDirAccessError(e)) { return false; } throw (e); @@ -847,20 +890,15 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); } } catch (e) { - if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') { - var msg = Zotero.localeJoin([ - Zotero.getString('startupError.databaseCannotBeOpened'), - Zotero.getString('startupError.checkPermissions') - ]); - Zotero.startupError = msg; - } + if (_checkDataDirAccessError(e)) {} // Storage busy - else if (e.message.endsWith('2153971713')) { + else if (e.message.includes('2153971713')) { Zotero.startupError = Zotero.getString('startupError.databaseInUse') + "\n\n" + Zotero.getString( "startupError.close" + (Zotero.isStandalone ? 'Firefox' : 'Standalone') ); - } else { + } + else { Zotero.startupError = Zotero.getString('startupError') + "\n\n" + (e.stack || e); } @@ -873,6 +911,39 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); return true; }); + + function _checkDataDirAccessError(e) { + if (e.name != 'NS_ERROR_FILE_ACCESS_DENIED' && !e.message.includes('2152857621')) { + return false; + } + + var msg = Zotero.getString('dataDir.databaseCannotBeOpened', Zotero.clientName) + + "\n\n" + + Zotero.getString('dataDir.checkPermissions', Zotero.clientName); + // If already using default directory, just show it + if (Zotero.DataDirectory.dir == Zotero.DataDirectory.defaultDir) { + msg += "\n\n" + Zotero.getString('dataDir.location', Zotero.DataDirectory.dir); + } + // Otherwise suggest moving to default, since there's a good chance this is due to security + // software preventing Zotero from accessing the selected directory (particularly if it's + // a Firefox profile) + else { + msg += "\n\n" + + Zotero.getString('dataDir.moveToDefaultLocation', Zotero.clientName) + + "\n\n" + + Zotero.getString( + 'dataDir.migration.failure.full.current', Zotero.DataDirectory.dir + ) + + "\n" + + Zotero.getString( + 'dataDir.migration.failure.full.recommended', Zotero.DataDirectory.defaultDir + ); + } + Zotero.startupError = msg; + return true; + } + + /** * Called when the DB has been released by another Zotero process to perform necessary * initialization steps diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 124f42bf22..3ed037097d 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -119,7 +119,13 @@ attachmentBasePath.clearBasePath.existingAttachments.singular = One existing at attachmentBasePath.clearBasePath.existingAttachments.plural = %S existing attachments within the old base directory will be converted to use absolute paths. attachmentBasePath.clearBasePath.button = Clear Base Directory Setting -dataDir.notFound = The Zotero data directory could not be found. +dataDir.databaseCannotBeOpened = The %S database cannot be opened. +dataDir.checkPermissions = Make sure you have read and write permissions for all files in the %1$S data directory and that security software isn’t preventing %1$S from accessing that directory. +dataDir.moveToDefaultLocation = You may be able to fix this problem by moving the data directory to the new default location in your home directory. %S will automatically detect the new location. +dataDir.location = Data Directory: %S +dataDir.notFound = The %S data directory could not be found. +dataDir.notFound.defaultFound = The %S data directory could not be found at %S, but a data directory was found at %S. Use this directory instead? +dataDir.useNewLocation = Use New Location dataDir.previousDir = Previous directory: dataDir.default = Default (%S) dataDir.useDefaultLocation = Use Default Location @@ -165,8 +171,6 @@ startupError = There was an error starting Zotero. startupError.databaseInUse = Your Zotero database is currently in use. Only one instance of Zotero using the same database may be opened simultaneously at this time. startupError.closeStandalone = If Zotero Standalone is open, please close it and restart Firefox. startupError.closeFirefox = If Firefox with the Zotero extension is open, please close it and restart Zotero Standalone. -startupError.databaseCannotBeOpened = The Zotero database cannot be opened. -startupError.checkPermissions = Make sure you have read and write permissions for all files in the Zotero data directory. startupError.zoteroVersionIsOlder = This version of Zotero is older than the version last used with your database. startupError.incompatibleDBVersion = This %1$S database requires %1$S %2$S or later. startupError.zoteroVersionIsOlder.current = Current version: %S