Migrate relative/persistent descriptors to string paths [DB reupgrade]
Absolute paths have been stored as strings on all platforms for a while, but old Mac persistent descriptors (Base64-encoded opaque alias records) could still exist in the DB. Additionally, relative paths for stored files were stored as Mozilla-specific opaque strings rather than UTF-8 strings. This adds a schema step to convert those to strings paths in the DB. Since Mac persistent descriptors aren't converted if the file isn't found, we still handle and (convert) old-style persistent descriptors if necessary when reading paths from the DB. This also moves path string handling -- converting a path to a prefixed string for stored or base-dir-relative files -- to the Zotero.Item#attachmentPath setter instead of save() so that reading it back immediately returns the correct value. One consequence is that the attachment link mode must now be set before setting the path. Zotero.Item#getFile() is now deprecated in favor of getFilePath() and getFilePathAsync() (which checks file existence). Zotero.File.directoryContains() now takes string paths instead of files.
This commit is contained in:
parent
80008fd13d
commit
9e356a7e63
5 changed files with 318 additions and 439 deletions
|
@ -94,7 +94,7 @@ Zotero.Attachments = new function(){
|
|||
contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile);
|
||||
|
||||
attachmentItem.attachmentContentType = contentType;
|
||||
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE);
|
||||
attachmentItem.attachmentPath = newFile.path;
|
||||
yield attachmentItem.save();
|
||||
}.bind(this))
|
||||
.then(function () {
|
||||
|
@ -198,7 +198,7 @@ Zotero.Attachments = new function(){
|
|||
newFile = destDir.clone();
|
||||
newFile.append(file.leafName);
|
||||
|
||||
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL);
|
||||
attachmentItem.attachmentPath = newFile.path;
|
||||
yield attachmentItem.save();
|
||||
}.bind(this))
|
||||
.then(function () {
|
||||
|
@ -365,9 +365,7 @@ Zotero.Attachments = new function(){
|
|||
destFile.append(fileName);
|
||||
|
||||
// Refetch item to update path
|
||||
attachmentItem.attachmentPath = Zotero.Attachments.getPath(
|
||||
destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL
|
||||
);
|
||||
attachmentItem.attachmentPath = destFile.path;
|
||||
yield attachmentItem.save();
|
||||
}.bind(this))
|
||||
.catch(function (e) {
|
||||
|
@ -656,9 +654,7 @@ Zotero.Attachments = new function(){
|
|||
var destFile = destDir.clone();
|
||||
destFile.append(fileName);
|
||||
|
||||
attachmentItem.attachmentPath = this.getPath(
|
||||
destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL
|
||||
);
|
||||
attachmentItem.attachmentPath = destFile.path;
|
||||
yield attachmentItem.save();
|
||||
}.bind(this))
|
||||
.catch(function (e) {
|
||||
|
@ -883,27 +879,12 @@ Zotero.Attachments = new function(){
|
|||
});
|
||||
|
||||
|
||||
/*
|
||||
* Gets a relative descriptor for imported attachments and a persistent
|
||||
* descriptor for files outside the storage directory
|
||||
*/
|
||||
this.getPath = function (file, linkMode) {
|
||||
file.QueryInterface(Components.interfaces.nsILocalFile);
|
||||
if (linkMode == self.LINK_MODE_IMPORTED_URL ||
|
||||
linkMode == self.LINK_MODE_IMPORTED_FILE) {
|
||||
var fileName = file.getRelativeDescriptor(file.parent);
|
||||
return 'storage:' + fileName;
|
||||
}
|
||||
return file.persistentDescriptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If file is within the attachment base directory, return a relative
|
||||
* If path is within the attachment base directory, return a relative
|
||||
* path prefixed by BASE_PATH_PLACEHOLDER. Otherwise, return unchanged.
|
||||
*/
|
||||
this.getBaseDirectoryRelativePath = function (path) {
|
||||
if (!path || path.indexOf(this.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
if (!path || path.startsWith(this.BASE_PATH_PLACEHOLDER)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -912,55 +893,24 @@ Zotero.Attachments = new function(){
|
|||
return path;
|
||||
}
|
||||
|
||||
// Get nsIFile for base directory
|
||||
var baseDir = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
baseDir.persistentDescriptor = basePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
return path;
|
||||
}
|
||||
|
||||
if (!baseDir.exists()) {
|
||||
Zotero.debug("Base directory '" + baseDir.path + "' doesn't exist", 2);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Get nsIFile for file
|
||||
var attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
attachmentFile.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
return path;
|
||||
}
|
||||
|
||||
if (Zotero.File.directoryContains(baseDir, attachmentFile)) {
|
||||
path = this.BASE_PATH_PLACEHOLDER
|
||||
+ attachmentFile.getRelativeDescriptor(baseDir);
|
||||
if (Zotero.File.directoryContains(basePath, path)) {
|
||||
basePath = OS.Path.normalize(basePath);
|
||||
path = OS.Path.normalize(path);
|
||||
path = this.BASE_PATH_PLACEHOLDER + path.substr(basePath.length + 1)
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a file from this path, if we can
|
||||
* Get an absolute path from this base-dir relative path, if we can
|
||||
*
|
||||
* @param {String} path Absolute path or relative path prefixed
|
||||
* by BASE_PATH_PLACEHOLDER
|
||||
* @param {Boolean} asFile Return nsIFile instead of path
|
||||
* @return {String|nsIFile|FALSE} Persistent descriptor string, file,
|
||||
* of FALSE if no path
|
||||
* @param {String} path - Absolute path or relative path prefixed by BASE_PATH_PLACEHOLDER
|
||||
* @return {String|false} - Absolute path, or FALSE if no path
|
||||
*/
|
||||
this.resolveRelativePath = function (path) {
|
||||
if (path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) != 0) {
|
||||
if (!path.startsWith(Zotero.Attachments.BASE_PATH_PLACEHOLDER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -970,34 +920,10 @@ Zotero.Attachments = new function(){
|
|||
return false;
|
||||
}
|
||||
|
||||
// Get file from base directory
|
||||
var baseDir = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
baseDir.persistentDescriptor = basePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug("Invalid base attachment path -- can't resolve'" + row.path + "'", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get file from relative path
|
||||
var relativePath = path.substr(
|
||||
Zotero.Attachments.BASE_PATH_PLACEHOLDER.length
|
||||
return OS.Path.join(
|
||||
OS.Path.normalize(basePath),
|
||||
path.substr(Zotero.Attachments.BASE_PATH_PLACEHOLDER.length)
|
||||
);
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.setRelativeDescriptor(baseDir, relativePath);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug("Invalid relative descriptor '" + relativePath + "'", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1346,15 +1272,14 @@ Zotero.Attachments = new function(){
|
|||
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
|
||||
}
|
||||
|
||||
// Get path
|
||||
if (file) {
|
||||
attachmentItem.attachmentPath = Zotero.Attachments.getPath(file, linkMode);
|
||||
}
|
||||
|
||||
attachmentItem.parentID = parentItemID;
|
||||
attachmentItem.attachmentLinkMode = linkMode;
|
||||
attachmentItem.attachmentContentType = contentType;
|
||||
attachmentItem.attachmentCharset = charset;
|
||||
if (file) {
|
||||
attachmentItem.attachmentPath = file.path;
|
||||
}
|
||||
|
||||
if (collections) {
|
||||
attachmentItem.setCollections(collections);
|
||||
}
|
||||
|
|
|
@ -1462,22 +1462,8 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
|||
let path = this.attachmentPath;
|
||||
let syncState = this.attachmentSyncState;
|
||||
|
||||
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
if (libraryType == 'publications') {
|
||||
throw new Error("Linked files cannot be added to My Publications");
|
||||
}
|
||||
|
||||
// Save attachment within attachment base directory as relative path
|
||||
if (Zotero.Prefs.get('saveRelativeAttachmentPath')) {
|
||||
path = Zotero.Attachments.getBaseDirectoryRelativePath(path);
|
||||
}
|
||||
// If possible, convert relative path to absolute
|
||||
else {
|
||||
let file = Zotero.Attachments.resolveRelativePath(path);
|
||||
if (file) {
|
||||
path = file.persistentDescriptor;
|
||||
}
|
||||
}
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE && libraryType != 'user') {
|
||||
throw new Error("Linked files can only be added to user library");
|
||||
}
|
||||
|
||||
let params = [
|
||||
|
@ -1958,109 +1944,15 @@ Zotero.Item.prototype.numAttachments = function(includeTrashed) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get an nsILocalFile for the attachment, or false for invalid paths
|
||||
*
|
||||
* Note: This no longer checks whether a file exists
|
||||
*
|
||||
* @return {nsILocalFile|false} An nsIFile, or false for invalid paths
|
||||
*/
|
||||
Zotero.Item.prototype.getFile = function () {
|
||||
if (arguments.length) {
|
||||
Zotero.debug("WARNING: Zotero.Item.prototype.getFile() no longer takes any arguments");
|
||||
}
|
||||
Zotero.debug("Zotero.Item.prototype.getFile() is deprecated -- use getFilePath[Async]()", 2);
|
||||
|
||||
if (!this.isAttachment()) {
|
||||
throw new Error("getFile() can only be called on attachment items");
|
||||
var path = this.getFilePath();
|
||||
if (path) {
|
||||
return Zotero.File.pathToFile(path);
|
||||
}
|
||||
|
||||
var linkMode = this.attachmentLinkMode;
|
||||
var path = this.attachmentPath;
|
||||
|
||||
// No associated files for linked URLs
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
Zotero.debug("Attachment path is empty", 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Imported file with relative path
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
|
||||
linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
|
||||
try {
|
||||
if (path.indexOf("storage:") == -1) {
|
||||
Zotero.debug("Invalid attachment path '" + path + "'", 2);
|
||||
throw ('Invalid path');
|
||||
}
|
||||
// Strip "storage:"
|
||||
path = path.substr(8);
|
||||
// setRelativeDescriptor() silently uses the parent directory on Windows
|
||||
// if the filename contains certain characters, so strip them —
|
||||
// but don't skip characters outside of XML range, since they may be
|
||||
// correct in the opaque relative descriptor string
|
||||
//
|
||||
// This is a bad place for this, since the change doesn't make it
|
||||
// back up to the sync server, but we do it to make sure we don't
|
||||
// accidentally use the parent dir. Syncing to OS X, which doesn't
|
||||
// exhibit this bug, will properly correct such filenames in
|
||||
// storage.js and propagate the change
|
||||
if (Zotero.isWin) {
|
||||
path = Zotero.File.getValidFileName(path, true);
|
||||
}
|
||||
var file = Zotero.Attachments.getStorageDirectory(this);
|
||||
file.QueryInterface(Components.interfaces.nsILocalFile);
|
||||
file.setRelativeDescriptor(file, path);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
|
||||
// See if this is a persistent path
|
||||
// (deprecated for imported attachments)
|
||||
Zotero.debug('Trying as persistent descriptor');
|
||||
|
||||
try {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
file.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid persistent descriptor', 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Linked file with relative path
|
||||
else if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
|
||||
path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
var file = Zotero.Attachments.resolveRelativePath(path);
|
||||
}
|
||||
else {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
|
||||
try {
|
||||
file.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
// See if this is an old relative path (deprecated)
|
||||
Zotero.debug('Invalid persistent descriptor -- trying relative');
|
||||
try {
|
||||
var refDir = (row.linkMode == this.LINK_MODE_LINKED_FILE)
|
||||
? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
|
||||
file.setRelativeDescriptor(refDir, path);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid relative descriptor', 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -2083,112 +1975,66 @@ Zotero.Item.prototype.getFilePath = function () {
|
|||
|
||||
if (!path) {
|
||||
Zotero.debug("Attachment path is empty", 2);
|
||||
if (!skipExistsCheck) {
|
||||
this._updateAttachmentStates(false);
|
||||
}
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Imported file with relative path
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
|
||||
linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
|
||||
try {
|
||||
if (path.indexOf("storage:") == -1) {
|
||||
Zotero.debug("Invalid attachment path '" + path + "'", 2);
|
||||
throw ('Invalid path');
|
||||
}
|
||||
// Strip "storage:"
|
||||
var path = path.substr(8);
|
||||
// setRelativeDescriptor() silently uses the parent directory on Windows
|
||||
// if the filename contains certain characters, so strip them —
|
||||
// but don't skip characters outside of XML range, since they may be
|
||||
// correct in the opaque relative descriptor string
|
||||
//
|
||||
// This is a bad place for this, since the change doesn't make it
|
||||
// back up to the sync server, but we do it to make sure we don't
|
||||
// accidentally use the parent dir. Syncing to OS X, which doesn't
|
||||
// exhibit this bug, will properly correct such filenames in
|
||||
// storage.js and propagate the change
|
||||
if (Zotero.isWin) {
|
||||
path = Zotero.File.getValidFileName(path, true);
|
||||
}
|
||||
var file = Zotero.Attachments.getStorageDirectory(this);
|
||||
file.QueryInterface(Components.interfaces.nsILocalFile);
|
||||
file.setRelativeDescriptor(file, path);
|
||||
if (path.indexOf("storage:") == -1) {
|
||||
Zotero.debug("Invalid attachment path '" + path + "'", 2);
|
||||
throw new Error("Invalid path");
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
|
||||
// See if this is a persistent path
|
||||
// (deprecated for imported attachments)
|
||||
Zotero.debug('Trying as persistent descriptor');
|
||||
|
||||
try {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
file.persistentDescriptor = path;
|
||||
|
||||
// If valid, convert this to a relative descriptor in the background
|
||||
OS.File.exists(file.path)
|
||||
.then(function (exists) {
|
||||
if (exists) {
|
||||
return Zotero.DB.queryAsync(
|
||||
"UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
["storage:" + file.leafName, this._id]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid persistent descriptor', 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Linked file with relative path
|
||||
else if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
|
||||
path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
var file = Zotero.Attachments.resolveRelativePath(path);
|
||||
if (!file) {
|
||||
this._updateAttachmentStates(false);
|
||||
// Strip "storage:"
|
||||
path = path.substr(8);
|
||||
|
||||
// Ignore .zotero* files that were relinked before we started blocking them
|
||||
if (path.startsWith(".zotero")) {
|
||||
Zotero.debug("Ignoring attachment file " + path, 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
|
||||
return OS.Path.join(
|
||||
OS.Path.normalize(Zotero.Attachments.getStorageDirectory(this).path), path
|
||||
);
|
||||
}
|
||||
|
||||
// Linked file with relative path
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
|
||||
path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
path = Zotero.Attachments.resolveRelativePath(path);
|
||||
if (!path) {
|
||||
this._updateAttachmentStates(false);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Old-style OS X persistent descriptor (Base64-encoded opaque alias record)
|
||||
//
|
||||
// These should only exist if they weren't converted in the 80 DB upgrade step because
|
||||
// the file couldn't be found.
|
||||
if (Zotero.isMac && path.startsWith('AAAA')) {
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
// See if this is an old relative path (deprecated)
|
||||
Zotero.debug('Invalid persistent descriptor -- trying relative');
|
||||
try {
|
||||
var refDir = (linkMode == this.LINK_MODE_LINKED_FILE)
|
||||
? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
|
||||
file.setRelativeDescriptor(refDir, path);
|
||||
// If valid, convert this to a persistent descriptor in the background
|
||||
OS.File.exists(file.path)
|
||||
.then(function (exists) {
|
||||
if (exists) {
|
||||
return Zotero.DB.queryAsync(
|
||||
"UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
[file.persistentDescriptor, this._id]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid relative descriptor', 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If valid, convert this to a regular string in the background
|
||||
Zotero.DB.queryAsync(
|
||||
"UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
[file.leafName, this._id]
|
||||
);
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
||||
return file.path;
|
||||
return path;
|
||||
};
|
||||
|
||||
|
||||
|
@ -2196,9 +2042,9 @@ Zotero.Item.prototype.getFilePath = function () {
|
|||
* Get the absolute path for the attachment, if it exists
|
||||
*
|
||||
* @return {Promise<String|false>} - A promise for either the absolute path of the attachment
|
||||
* or false for invalid paths or if the file doesn't exist
|
||||
* or false for invalid paths or if the file doesn't exist
|
||||
*/
|
||||
Zotero.Item.prototype.getFilePathAsync = Zotero.Promise.coroutine(function* (skipExistsCheck) {
|
||||
Zotero.Item.prototype.getFilePathAsync = Zotero.Promise.coroutine(function* () {
|
||||
if (!this.isAttachment()) {
|
||||
throw new Error("getFilePathAsync() can only be called on attachment items");
|
||||
}
|
||||
|
@ -2213,125 +2059,93 @@ Zotero.Item.prototype.getFilePathAsync = Zotero.Promise.coroutine(function* (ski
|
|||
|
||||
if (!path) {
|
||||
Zotero.debug("Attachment path is empty", 2);
|
||||
if (!skipExistsCheck) {
|
||||
this._updateAttachmentStates(false);
|
||||
}
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Imported file with relative path
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
|
||||
linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
|
||||
try {
|
||||
if (path.indexOf("storage:") == -1) {
|
||||
Zotero.debug("Invalid attachment path '" + path + "'", 2);
|
||||
throw ('Invalid path');
|
||||
}
|
||||
// Strip "storage:"
|
||||
var path = path.substr(8);
|
||||
// setRelativeDescriptor() silently uses the parent directory on Windows
|
||||
// if the filename contains certain characters, so strip them —
|
||||
// but don't skip characters outside of XML range, since they may be
|
||||
// correct in the opaque relative descriptor string
|
||||
//
|
||||
// This is a bad place for this, since the change doesn't make it
|
||||
// back up to the sync server, but we do it to make sure we don't
|
||||
// accidentally use the parent dir. Syncing to OS X, which doesn't
|
||||
// exhibit this bug, will properly correct such filenames in
|
||||
// storage.js and propagate the change
|
||||
//
|
||||
// The one exception on other platforms is '/', which is interpreted
|
||||
// as a directory by setRelativeDescriptor, so strip in that case too.
|
||||
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);
|
||||
file.QueryInterface(Components.interfaces.nsILocalFile);
|
||||
file.setRelativeDescriptor(file, path);
|
||||
if (path.indexOf("storage:") == -1) {
|
||||
Zotero.debug("Invalid attachment path '" + path + "'", 2);
|
||||
throw new Error("Invalid path");
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
|
||||
// See if this is a persistent path
|
||||
// (deprecated for imported attachments)
|
||||
Zotero.debug('Trying as persistent descriptor');
|
||||
|
||||
try {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
file.persistentDescriptor = path;
|
||||
|
||||
// If valid, convert this to a relative descriptor
|
||||
if (!skipExistsCheck && file.exists()) {
|
||||
yield Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
["storage:" + file.leafName, this._id]);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid persistent descriptor', 2);
|
||||
if (!skipExistsCheck) {
|
||||
this._updateAttachmentStates(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip "storage:"
|
||||
path = path.substr(8);
|
||||
|
||||
// Ignore .zotero* files that were relinked before we started blocking them
|
||||
if (path.startsWith(".zotero")) {
|
||||
Zotero.debug("Ignoring attachment file " + path, 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Linked file with relative path
|
||||
else if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
|
||||
path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
var file = Zotero.Attachments.resolveRelativePath(path);
|
||||
if (!skipExistsCheck && !file) {
|
||||
|
||||
path = OS.Path.join(
|
||||
OS.Path.normalize(Zotero.Attachments.getStorageDirectory(this).path), path
|
||||
);
|
||||
|
||||
if (!(yield OS.File.exists(path))) {
|
||||
Zotero.debug("Attachment file '" + path + "' not found", 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Linked file with relative path
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
|
||||
path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
|
||||
path = Zotero.Attachments.resolveRelativePath(path);
|
||||
if (!path) {
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
if (!(yield OS.File.exists(path))) {
|
||||
Zotero.debug("Attachment file '" + path + "' not found", 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Old-style OS X persistent descriptor (Base64-encoded opaque alias record)
|
||||
//
|
||||
// These should only exist if they weren't converted in the 80 DB upgrade step because
|
||||
// the file couldn't be found
|
||||
if (Zotero.isMac && path.startsWith('AAAA')) {
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
// See if this is an old relative path (deprecated)
|
||||
Zotero.debug('Invalid persistent descriptor -- trying relative');
|
||||
try {
|
||||
var refDir = (linkMode == this.LINK_MODE_LINKED_FILE)
|
||||
? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
|
||||
file.setRelativeDescriptor(refDir, path);
|
||||
// If valid, convert this to a persistent descriptor
|
||||
if (!skipExistsCheck && file.exists()) {
|
||||
yield Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
[file.persistentDescriptor, this._id]);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid relative descriptor', 2);
|
||||
if (!skipExistsCheck) {
|
||||
this._updateAttachmentStates(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If valid, convert this to a regular string
|
||||
yield Zotero.DB.queryAsync(
|
||||
"UPDATE itemAttachments SET path=? WHERE itemID=?",
|
||||
[file.leafName, this._id]
|
||||
);
|
||||
|
||||
if (!(yield OS.File.exists(file.path))) {
|
||||
Zotero.debug("Attachment file '" + file.path + "' not found", 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
||||
var path = file.path;
|
||||
|
||||
if (!skipExistsCheck && !(yield OS.File.exists(path))) {
|
||||
if (!(yield OS.File.exists(path))) {
|
||||
Zotero.debug("Attachment file '" + path + "' not found", 2);
|
||||
this._updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!skipExistsCheck) {
|
||||
this._updateAttachmentStates(true);
|
||||
}
|
||||
return file.path;
|
||||
return path;
|
||||
});
|
||||
|
||||
|
||||
|
@ -2557,7 +2371,7 @@ Zotero.Item.prototype.relinkAttachmentFile = Zotero.Promise.coroutine(function*
|
|||
return false;
|
||||
}
|
||||
|
||||
this.attachmentPath = Zotero.Attachments.getPath(Zotero.File.pathToFile(newPath), linkMode);
|
||||
this.attachmentPath = newPath;
|
||||
|
||||
yield this.saveTx({
|
||||
skipDateModifiedUpdate: true,
|
||||
|
@ -2769,6 +2583,12 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentFilename', {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Returns raw attachment path string as stored in DB
|
||||
* (e.g., "storage:foo.pdf", "attachments:foo/bar.pdf", "/Users/foo/Desktop/bar.pdf")
|
||||
*
|
||||
* Can be set as absolute path or prefixed string ("storage:foo.pdf")
|
||||
*/
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', {
|
||||
get: function() {
|
||||
if (!this.isAttachment()) {
|
||||
|
@ -2778,17 +2598,53 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', {
|
|||
},
|
||||
set: function(val) {
|
||||
if (!this.isAttachment()) {
|
||||
throw (".attachmentPath can only be set for attachment items");
|
||||
throw new Error(".attachmentPath can only be set for attachment items");
|
||||
}
|
||||
|
||||
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
throw ('attachmentPath cannot be set for link attachments');
|
||||
if (typeof val != 'string') {
|
||||
throw new Error(".attachmentPath must be a string");
|
||||
}
|
||||
|
||||
var linkMode = this.attachmentLinkMode;
|
||||
if (linkMode === null) {
|
||||
throw new Error("Link mode must be set before setting attachment path");
|
||||
}
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
throw new Error('attachmentPath cannot be set for link attachments');
|
||||
}
|
||||
|
||||
if (!val) {
|
||||
val = '';
|
||||
}
|
||||
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
if (this._libraryID) {
|
||||
let libraryType = Zotero.Libraries.get(this._libraryID).libraryType;
|
||||
if (libraryType != 'user') {
|
||||
throw new Error("Linked files can only be added to user library");
|
||||
}
|
||||
}
|
||||
|
||||
// If base directory is enabled, save attachment within as relative path
|
||||
if (Zotero.Prefs.get('saveRelativeAttachmentPath')) {
|
||||
val = Zotero.Attachments.getBaseDirectoryRelativePath(val);
|
||||
}
|
||||
// Otherwise, convert relative path to absolute if possible
|
||||
else {
|
||||
val = Zotero.Attachments.resolveRelativePath(val) || val;
|
||||
}
|
||||
}
|
||||
else if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
|
||||
linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
|
||||
if (!val.startsWith('storage:')) {
|
||||
let storagePath = Zotero.Attachments.getStorageDirectory(this).path;
|
||||
if (!val.startsWith(storagePath)) {
|
||||
throw new Error("Imported file path must be within storage directory");
|
||||
}
|
||||
val = 'storage:' + OS.Path.basename(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (val == this.attachmentPath) {
|
||||
return;
|
||||
}
|
||||
|
@ -2797,28 +2653,12 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', {
|
|||
this._changed.attachmentData = {};
|
||||
}
|
||||
this._changed.attachmentData.path = true;
|
||||
|
||||
this._attachmentPath = val;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Force an update of the attachment path and resave the item
|
||||
*
|
||||
* This is used when changing the attachment base directory, since relative
|
||||
* path handling is done on item save.
|
||||
*/
|
||||
Zotero.Item.prototype.updateAttachmentPath = function () {
|
||||
if (!this._changed.attachmentData) {
|
||||
this._changed.attachmentData = {};
|
||||
}
|
||||
this._changed.attachmentData.path = true;
|
||||
this.save({
|
||||
skipDateModifiedUpdate: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
|
||||
get: function() {
|
||||
if (!this.isAttachment()) {
|
||||
|
|
|
@ -643,26 +643,14 @@ Zotero.File = new function(){
|
|||
* Check whether a directory is an ancestor directory of another directory/file
|
||||
*/
|
||||
this.directoryContains = function (dir, file) {
|
||||
if (!dir.isDirectory()) {
|
||||
throw new Error("dir must be a directory");
|
||||
}
|
||||
if (typeof dir != 'string') throw new Error("dir must be a string");
|
||||
if (typeof file != 'string') throw new Error("file must be a string");
|
||||
|
||||
if (dir.exists()) {
|
||||
dir.normalize();
|
||||
}
|
||||
if (file.exists()) {
|
||||
file.normalize();
|
||||
}
|
||||
dir = OS.Path.normalize(dir);
|
||||
file = OS.Path.normalize(file);
|
||||
|
||||
if (!dir.path) {
|
||||
throw new Error("dir.path is empty");
|
||||
}
|
||||
if (!file.path) {
|
||||
throw new Error("file.path is empty");
|
||||
}
|
||||
|
||||
return file.path.indexOf(dir.path) == 0;
|
||||
}
|
||||
return file.startsWith(dir);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -2162,6 +2162,8 @@ Zotero.Schema = new function(){
|
|||
yield Zotero.DB.queryAsync("DROP INDEX IF EXISTS itemAttachments_syncState");
|
||||
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState)");
|
||||
|
||||
yield _migrateUserData_80_filePaths();
|
||||
|
||||
yield Zotero.DB.queryAsync("ALTER TABLE collectionItems RENAME TO collectionItemsOld");
|
||||
yield Zotero.DB.queryAsync("CREATE TABLE collectionItems (\n collectionID INT NOT NULL,\n itemID INT NOT NULL,\n orderIndex INT NOT NULL DEFAULT 0,\n PRIMARY KEY (collectionID, itemID),\n FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
|
||||
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO collectionItems SELECT * FROM collectionItemsOld");
|
||||
|
@ -2286,6 +2288,75 @@ Zotero.Schema = new function(){
|
|||
//
|
||||
// Longer functions for specific upgrade steps
|
||||
//
|
||||
|
||||
/**
|
||||
* Convert Mozilla-specific relative descriptors below storage and base directories to UTF-8
|
||||
* paths using '/' separators
|
||||
*/
|
||||
var _migrateUserData_80_filePaths = Zotero.Promise.coroutine(function* () {
|
||||
var rows = yield Zotero.DB.queryAsync("SELECT itemID, libraryID, key, linkMode, path FROM items JOIN itemAttachments USING (itemID) WHERE path != ''");
|
||||
var tmpDirFile = Zotero.getTempDirectory();
|
||||
var tmpFilePath = OS.Path.normalize(tmpDirFile.path);
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
let libraryKey = row.libraryID + "/" + row.key;
|
||||
let path = row.path;
|
||||
let prefix = path.match(/^(attachments|storage):/);
|
||||
if (prefix) {
|
||||
prefix = prefix[0];
|
||||
let relPath = path.substr(prefix.length)
|
||||
let file = tmpDirFile.clone();
|
||||
file.setRelativeDescriptor(file, relPath);
|
||||
path = OS.Path.normalize(file.path);
|
||||
|
||||
// setRelativeDescriptor() silently uses the parent directory on Windows
|
||||
// if the filename contains certain characters, so strip them —
|
||||
// but don't skip characters outside of XML range, since they may be
|
||||
// correct in the opaque relative descriptor string
|
||||
//
|
||||
// This is a bad place for this, since the change doesn't make it
|
||||
// back up to the sync server, but we do it to make sure we don't
|
||||
// accidentally use the parent dir.
|
||||
if (path == tmpFilePath) {
|
||||
file.setRelativeDescriptor(file, Zotero.File.getValidFileName(relPath, true));
|
||||
path = OS.Path.normalize(file.path);
|
||||
if (path == tmpFilePath) {
|
||||
Zotero.logError("Cannot fix relative descriptor for item " + libraryKey + " -- not converting path");
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
Zotero.logError("Filtered relative descriptor for item " + libraryKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize path, and then convert '\' to '/'. As long as normalize() is run on the
|
||||
// path before use, it doesn't matter which separator it uses, but we might as well
|
||||
// be consistent.
|
||||
path = path.replace(/\\/g, '/');
|
||||
if (!path.startsWith(tmpFilePath)) {
|
||||
Zotero.logError(path + " does not start with temp path -- not converting relative path for item " + libraryKey);
|
||||
continue;
|
||||
}
|
||||
path = prefix + path.substr(tmpFilePath.length + 1);
|
||||
}
|
||||
else {
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.persistentDescriptor = path;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Invalid persistent descriptor for item " + libraryKey + " -- not converting path");
|
||||
continue;
|
||||
}
|
||||
path = file.path;
|
||||
}
|
||||
|
||||
yield Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?", [path, row.itemID]);
|
||||
}
|
||||
})
|
||||
|
||||
var _migrateUserData_80_relations = Zotero.Promise.coroutine(function* () {
|
||||
yield Zotero.DB.queryAsync("CREATE TABLE relationPredicates (\n predicateID INTEGER PRIMARY KEY,\n predicate TEXT UNIQUE\n)");
|
||||
|
||||
|
|
|
@ -535,10 +535,65 @@ describe("Zotero.Item", function () {
|
|||
// Check full path
|
||||
var file = Zotero.Attachments.getStorageDirectory(item);
|
||||
file.append(filename);
|
||||
assert.equal(item.getFile().path, file.path);
|
||||
assert.equal(item.getFilePath(), file.path);
|
||||
});
|
||||
})
|
||||
|
||||
describe("#attachmentPath", function () {
|
||||
it("should return an absolute path for a linked attachment", function* () {
|
||||
var file = getTestDataDirectory();
|
||||
file.append('test.png');
|
||||
var item = yield Zotero.Attachments.linkFromFile({ file });
|
||||
assert.equal(item.attachmentPath, file.path);
|
||||
})
|
||||
|
||||
it("should return a prefixed path for an imported file", function* () {
|
||||
var file = getTestDataDirectory();
|
||||
file.append('test.png');
|
||||
var item = yield Zotero.Attachments.importFromFile({ file });
|
||||
|
||||
assert.equal(item.attachmentPath, "storage:test.png");
|
||||
})
|
||||
|
||||
it("should set a prefixed relative path for a path within the defined base directory", function* () {
|
||||
var dir = getTestDataDirectory().path;
|
||||
var dirname = OS.Path.basename(dir);
|
||||
var baseDir = OS.Path.dirname(dir);
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', true)
|
||||
Zotero.Prefs.set('baseAttachmentPath', baseDir)
|
||||
|
||||
var file = OS.Path.join(dir, 'test.png');
|
||||
|
||||
var item = new Zotero.Item('attachment');
|
||||
item.attachmentLinkMode = 'linked_file';
|
||||
item.attachmentPath = file;
|
||||
|
||||
assert.equal(item.attachmentPath, "attachments:data/test.png");
|
||||
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', false)
|
||||
Zotero.Prefs.clear('baseAttachmentPath')
|
||||
})
|
||||
|
||||
it("should return a prefixed path for a linked attachment within the defined base directory", function* () {
|
||||
var dir = getTestDataDirectory().path;
|
||||
var dirname = OS.Path.basename(dir);
|
||||
var baseDir = OS.Path.dirname(dir);
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', true)
|
||||
Zotero.Prefs.set('baseAttachmentPath', baseDir)
|
||||
|
||||
var file = OS.Path.join(dir, 'test.png');
|
||||
|
||||
var item = yield Zotero.Attachments.linkFromFile({
|
||||
file: Zotero.File.pathToFile(file)
|
||||
});
|
||||
|
||||
assert.equal(item.attachmentPath, "attachments:data/test.png");
|
||||
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', false)
|
||||
Zotero.Prefs.clear('baseAttachmentPath')
|
||||
})
|
||||
})
|
||||
|
||||
describe("#renameAttachmentFile()", function () {
|
||||
it("should rename an attached file", function* () {
|
||||
var file = getTestDataDirectory();
|
||||
|
|
Loading…
Reference in a new issue