File Issues: Greatest Hits, 2006-2015

- When relinking a missing stored file, copy it into the attachment's
  storage directory automatically
  - Previously, selecting a file outside the attachment subdir would
    just result in a missing attachment, since it only looks for stored
    files within the subdir
- Display an error message if a Windows shortcut (.lnk) is added via
  drag-and-drop or via a file dialog on non-Windows systems, until we
  can figure out how to determine the original file
  - Shortcuts can cause errors during syncing, for unclear reasons
  - Neither nsIFile::copyToFollowingLinks() nor nsIFile::target work for
    me to get the original file, even when nsIFile::isSymlink() returns
    true
  - Windows file dialogs seem to automatically resolve shortcuts, so
    it's only an issue there for drag-and-drop
- Disallow hidden files from being selected in relink dialog
  - I think some people on Windows with hidden files shown relink the
    .zotero* files that show up when they click Locate, which causes
    file sync errors. Which brings us to...
- Fix file sync errors for *.lnk and .zotero* files
- Ignore existing .zotero* attachment files, treating the files as
  missing instead to encourage relinking
- Strip leading period in getValidFileName() to prevent added files from
  being hidden
    - This allows hidden files to be added explicitly; they just won't
      stay that way in the storage directory

(These things should have tests, but that will have to happen on the 5.0
branch.)
This commit is contained in:
Dan Stillman 2015-06-24 05:38:41 -04:00
parent 0511d37b07
commit dfb28ff3f6
8 changed files with 128 additions and 34 deletions

View file

@ -644,7 +644,7 @@ var wpdCommon = {
aDir.initWithPath(destdir);
aFile.copyTo(aDir, destfile);
aFile.copyToFollowingLinks(aDir, destfile);
return true; // Added by Dan S. for Zotero
},

View file

@ -55,6 +55,10 @@ Zotero.Attachments = new function(){
throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()");
}
if (file.leafName.endsWith(".lnk")) {
throw new Error("Cannot add Windows shortcut");
}
Zotero.DB.beginTransaction();
try {
@ -165,7 +169,7 @@ Zotero.Attachments = new function(){
var storageDir = Zotero.getStorageDirectory();
var destDir = this.getStorageDirectory(itemID);
_moveOrphanedDirectory(destDir);
file.parent.copyTo(storageDir, destDir.leafName);
file.parent.copyToFollowingLinks(storageDir, destDir.leafName);
// Point to copied file
var newFile = destDir.clone();

View file

@ -2863,6 +2863,11 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
if (Zotero.isWin || path.indexOf('/') != -1) {
path = Zotero.File.getValidFileName(path, true);
}
// Ignore .zotero* files that were relinked before we started blocking them
if (path.startsWith(".zotero")) {
Zotero.debug("Ignoring attachment file " + path, 2);
return false;
}
var file = Zotero.Attachments.getStorageDirectory(this.id);
file.QueryInterface(Components.interfaces.nsILocalFile);
file.setRelativeDescriptor(file, path);
@ -3109,15 +3114,47 @@ Zotero.Item.prototype.relinkAttachmentFile = function(file, skipItemUpdate) {
throw('Cannot relink linked URL in Zotero.Items.relinkAttachmentFile()');
}
if (file.leafName.endsWith(".lnk")) {
throw new Error("Cannot relink to Windows shortcut");
}
var newName = Zotero.File.getValidFileName(file.leafName);
if (!newName) {
throw ("No valid characters in filename after filtering in Zotero.Item.relinkAttachmentFile()");
}
// Rename file to filtered name if necessary
if (file.leafName != newName) {
Zotero.debug("Renaming file '" + file.leafName + "' to '" + newName + "'");
file.moveTo(null, newName);
try {
// If selected file isn't in the attachment's storage directory,
// copy it in and use that one instead
var storageDir = Zotero.Attachments.getStorageDirectory(this.id);
if (this.isImportedAttachment() && !file.parent.equals(storageDir)) {
// If file with same name already exists in the storage directory,
// move it out of the way
let targetFile = storageDir.clone();
targetFile.append(newName);
let renamedFile;
if (targetFile.exists()) {
renamedFile = targetFile.clone();
renamedFile.moveTo(null, newName + ".bak");
}
Zotero.File.copyToUnique(file, targetFile);
file = targetFile;
// Delete backup file
if (renamedFile) {
renamedFile.remove(false);
}
}
// Rename file to filtered name if necessary
else if (file.leafName != newName) {
Zotero.debug("Renaming file '" + file.leafName + "' to '" + newName + "'");
file.moveTo(null, newName);
}
}
catch (e) {
Zotero.logError(e);
return false;
}
var path = Zotero.Attachments.getPath(file, linkMode);
@ -3128,7 +3165,7 @@ Zotero.Item.prototype.relinkAttachmentFile = function(file, skipItemUpdate) {
skipClientDateModifiedUpdate: skipItemUpdate
});
return false;
return true;
}
@ -3342,7 +3379,8 @@ Zotero.Item.prototype.__defineGetter__('attachmentPath', function () {
var sql = "SELECT path FROM itemAttachments WHERE itemID=?";
var path = Zotero.DB.valueQuery(sql, this.id);
if (!path) {
// Ignore .zotero* files that were relinked before we started blocking them
if (!path || path.startsWith('.zotero')) {
path = '';
}
this._attachmentPath = path;

View file

@ -426,7 +426,7 @@ Zotero.File = new function(){
newFile.remove(null);
// Copy file to unique name
file.copyTo(newFile.parent, newName);
file.copyToFollowingLinks(newFile.parent, newName);
return newFile;
}
@ -442,7 +442,7 @@ Zotero.File = new function(){
while (otherFiles.hasMoreElements()) {
var file = otherFiles.getNext();
file.QueryInterface(Components.interfaces.nsIFile);
file.copyTo(newDir, null);
file.copyToFollowingLinks(newDir, null);
}
}
@ -505,6 +505,8 @@ Zotero.File = new function(){
// Strip characters not valid in XML, since they won't sync and they're probably unwanted
fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '');
}
// Don't allow hidden files
fileName = fileName.replace(/^\./, '');
// Don't allow blank or illegal filenames
if (!fileName || fileName == '.' || fileName == '..') {
fileName = '_';

View file

@ -2810,7 +2810,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
}
}
parentDir.copyTo(destDir, newName ? newName : dirName);
parentDir.copyToFollowingLinks(destDir, newName ? newName : dirName);
// Store nsIFile
if (useTemp) {
@ -2851,7 +2851,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
}
}
file.copyTo(destDir, newName ? newName : null);
file.copyToFollowingLinks(destDir, newName ? newName : null);
// Store nsIFile
if (useTemp) {
@ -3219,6 +3219,14 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
var itemID = Zotero.Attachments.linkFromFile(file, sourceItemID);
}
else {
if (file.leafName.endsWith(".lnk")) {
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file.path);
Zotero.DB.commitTransaction();
continue;
}
var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID);
// If moving, delete original file
if (dragData.dropEffect == 'move') {

View file

@ -1486,6 +1486,10 @@ Zotero.Sync.Storage = new function () {
if (!file) {
throw ("Empty path for item " + item.key + " in " + funcName);
}
// Don't save Windows aliases
if (file.leafName.endsWith('.lnk')) {
return false;
}
var fileName = file.leafName;
var renamed = false;
@ -1899,8 +1903,9 @@ Zotero.Sync.Storage = new function () {
params.push(Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD);
}
sql += ") "
// Skip attachments with empty path, which can't be saved
+ "AND path!=''";
// Skip attachments with empty path, which can't be saved, and files with .zotero*
// paths, which have somehow ended up in some users' libraries
+ "AND path!='' AND path NOT LIKE 'storage:.zotero%'";
var itemIDs = Zotero.DB.columnQuery(sql, params);
if (!itemIDs) {
return [];

View file

@ -3116,8 +3116,16 @@ var ZoteroPane = new function()
var attachmentID;
if(link)
attachmentID = Zotero.Attachments.linkFromFile(file, id);
else
else {
if (file.leafName.endsWith(".lnk")) {
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file.path);
continue;
}
attachmentID = Zotero.Attachments.importFromFile(file, id, libraryID);
}
if(attachmentID && !id)
{
@ -3757,6 +3765,16 @@ var ZoteroPane = new function()
}
// TODO: Figure out a functioning way to get the original path and just copy the real file
this.displayCannotAddShortcutMessage = function (path) {
Zotero.alert(
null,
Zotero.getString("general.error"),
Zotero.getString("file.error.cannotAddShortcut") + (path ? "\n\n" + path : "")
);
}
function showAttachmentNotFoundDialog(itemID, noLocate) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
@ -3951,25 +3969,43 @@ var ZoteroPane = new function()
throw('Item ' + itemID + ' not found in ZoteroPane_Local.relinkAttachment()');
}
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen);
var file = item.getFile(false, true);
var dir = Zotero.File.getClosestDirectory(file);
if (dir) {
dir.QueryInterface(Components.interfaces.nsILocalFile);
fp.displayDirectory = dir;
}
fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
if (fp.show() == nsIFilePicker.returnOK) {
var file = fp.file;
file.QueryInterface(Components.interfaces.nsILocalFile);
item.relinkAttachmentFile(file);
while (true) {
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen);
var file = item.getFile(false, true);
var dir = Zotero.File.getClosestDirectory(file);
if (dir) {
dir.QueryInterface(Components.interfaces.nsILocalFile);
fp.displayDirectory = dir;
}
fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
if (fp.show() == nsIFilePicker.returnOK) {
let file = fp.file;
file.QueryInterface(Components.interfaces.nsILocalFile);
// Disallow hidden files
// TODO: Display a message
if (file.leafName.startsWith('.')) {
continue;
}
// Disallow Windows shortcuts
if (file.leafName.endsWith(".lnk")) {
this.displayCannotAddShortcutMessage(file.path);
continue;
}
item.relinkAttachmentFile(file);
break;
}
break;
}
}

View file

@ -963,6 +963,7 @@ file.accessError.message.windows = Check that the file is not currently in use,
file.accessError.message.other = Check that the file is not currently in use and that its permissions allow write access.
file.accessError.restart = Restarting your computer or disabling security software may also help.
file.accessError.showParentDir = Show Parent Directory
file.error.cannotAddShortcut = Shortcut files cannot be added directly. Please select the original file.
lookup.failure.title = Lookup Failed
lookup.failure.description = Zotero could not find a record for the specified identifier. Please verify the identifier and try again.