Skip to content

Commit

Permalink
Automatic attachment file renaming changes
Browse files Browse the repository at this point in the history
Automatic renaming is now done for dragging of an external file onto an
item (as added in 7cb95f4) as well as dragging as a linked file,
dragging a non-native (not text or HTML) URL, "Attach Link to File…",
"Attach Stored Copy of File…", and "Retrieve Metadata for PDF". It only
applies if a single file is being added and if the parent item has no
non-HTML attachments. By default, the renaming only applies to PDFs, but
this can be changed with the renameAttachmentFiles.automatic.fileTypes
hidden pref.

A new General pref, "Automatically rename attachment files using parent
metadata", controls whether the renaming happens.

Files saved via web translators are renamed regardless of this pref,
because they would often be gibberish otherwise.

Closes #113
  • Loading branch information
dstillman committed Feb 27, 2018
1 parent 68879a0 commit f8b41c9
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 92 deletions.
2 changes: 2 additions & 0 deletions chrome/content/zotero/preferences/preferences_general.xul
Expand Up @@ -37,6 +37,7 @@
<preference id="pref-reportTranslationFailure" name="extensions.zotero.reportTranslationFailure" type="bool"/>
<preference id="pref-automaticSnapshots" name="extensions.zotero.automaticSnapshots" type="bool"/>
<preference id="pref-downloadAssociatedFiles" name="extensions.zotero.downloadAssociatedFiles" type="bool"/>
<preference id="pref-renameAttachmentFiles" name="extensions.zotero.renameAttachmentFiles.automatic" type="bool"/>
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
<preference id="pref-trashAutoEmptyDays" name="extensions.zotero.trashAutoEmptyDays" type="int"/>

Expand Down Expand Up @@ -122,6 +123,7 @@
label="&zotero.preferences.automaticSnapshots;"
preference="pref-automaticSnapshots"/>
<checkbox label="&zotero.preferences.downloadAssociatedFiles;" preference="pref-downloadAssociatedFiles"/>
<checkbox label="&zotero.preferences.renameAttachmentFiles;" preference="pref-renameAttachmentFiles"/>
<checkbox label="&zotero.preferences.automaticTags;" preference="pref-automaticTags"/>
<hbox align="center">
<label value="&zotero.preferences.trashAutoEmptyDaysPre;"/>
Expand Down
45 changes: 42 additions & 3 deletions chrome/content/zotero/xpcom/attachments.js
Expand Up @@ -245,9 +245,18 @@ Zotero.Attachments = new function(){


/**
* @param {Object} options - 'libraryID', 'url', 'parentItemID', 'collections', 'title',
* 'fileBaseName', 'contentType', 'referrer', 'cookieSandbox',
* 'saveOptions'
* @param {Object} options
* @param {Integer} options.libraryID
* @param {String} options.url
* @param {Integer} [options.parentItemID]
* @param {Integer[]} [options.collections]
* @param {String} [options.title]
* @param {String} [options.fileBaseName]
* @param {Boolean} [options.renameIfAllowedType=false]
* @param {String} [options.contentType]
* @param {String} [options.referrer]
* @param {CookieSandbox} [options.cookieSandbox]
* @param {Object} [options.saveOptions]
* @return {Promise<Zotero.Item>} - A promise for the created attachment item
*/
this.importFromURL = Zotero.Promise.coroutine(function* (options) {
Expand All @@ -257,6 +266,7 @@ Zotero.Attachments = new function(){
var collections = options.collections;
var title = options.title;
var fileBaseName = options.fileBaseName;
var renameIfAllowedType = options.renameIfAllowedType;
var contentType = options.contentType;
var referrer = options.referrer;
var cookieSandbox = options.cookieSandbox;
Expand Down Expand Up @@ -320,6 +330,11 @@ Zotero.Attachments = new function(){

// Save using remote web browser persist
var externalHandlerImport = Zotero.Promise.coroutine(function* (contentType) {
// Rename attachment
if (renameIfAllowedType && !fileBaseName && this.getRenamedFileTypes().includes(contentType)) {
let parentItem = Zotero.Items.get(parentItemID);
fileBaseName = this.getFileBaseNameFromItem(parentItem);
}
if (fileBaseName) {
let ext = _getExtensionFromURL(url, contentType);
var fileName = fileBaseName + (ext != '' ? '.' + ext : '');
Expand Down Expand Up @@ -804,6 +819,30 @@ Zotero.Attachments = new function(){
}


this.getRenamedFileTypes = function () {
try {
var types = Zotero.Prefs.get('renameAttachmentFiles.automatic.fileTypes');
return types ? types.split(',') : [];
}
catch (e) {
return [];
}
};


this.getRenamedFileBaseNameIfAllowedType = async function (parentItem, file) {
var types = this.getRenamedFileTypes();
var contentType = file.endsWith('.pdf')
// Don't bother reading file if there's a .pdf extension
? 'application/pdf'
: await Zotero.MIME.getMIMETypeFromFile(file);
if (!types.includes(contentType)) {
return false;
}
return this.getFileBaseNameFromItem(parentItem);
}


/**
* Create directory for attachment files within storage directory
*
Expand Down
9 changes: 9 additions & 0 deletions chrome/content/zotero/xpcom/data/item.js
Expand Up @@ -2120,6 +2120,15 @@ Zotero.Item.prototype.numAttachments = function (includeTrashed) {
}


Zotero.Item.prototype.numNonHTMLFileAttachments = function () {
this._requireData('childItems');
return this.getAttachments()
.map(itemID => Zotero.Items.get(itemID))
.filter(item => item.isFileAttachment() && item.attachmentContentType != 'text/html')
.length;
};


Zotero.Item.prototype.getFile = function () {
Zotero.debug("Zotero.Item.prototype.getFile() is deprecated -- use getFilePath[Async]()", 2);

Expand Down
61 changes: 43 additions & 18 deletions chrome/content/zotero/xpcom/itemTreeView.js
Expand Up @@ -3231,26 +3231,22 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or

var notifierQueue = new Zotero.Notifier.Queue;
try {
// If there's a single file being added to a parent, automatic renaming is enabled,
// and there are no other non-HTML attachments, we'll rename the file as long as it's
// an allowed type. The dragged data could be a URL, so we don't yet know the file type.
// This should be kept in sync with ZoteroPane.addAttachmentFromDialog().
let renameIfAllowedType = false;
let parentItem;
let numExistingFileAttachments;
if (parentItemID) {
if (parentItemID && data.length == 1 && Zotero.Prefs.get('renameAttachmentFiles.automatic')) {
parentItem = Zotero.Items.get(parentItemID);
numExistingFileAttachments = parentItem.getAttachments()
.map(itemID => Zotero.Items.get(itemID))
.filter(item => item.isFileAttachment())
.length;
if (!parentItem.numNonHTMLFileAttachments()) {
renameIfAllowedType = true;
}
}

for (var i=0; i<data.length; i++) {
var file = data[i];

let fileBaseName;
// If only one item is being dragged and it's the only attachment, run
// "Rename File from Parent Metadata" automatically
if (data.length == 1 && parentItem && !numExistingFileAttachments) {
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
}

if (dataType == 'text/x-moz-url') {
var url = data[i];
if (url.indexOf('file:///') == 0) {
Expand Down Expand Up @@ -3281,7 +3277,7 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
yield Zotero.Attachments.importFromURL({
libraryID: targetLibraryID,
url,
fileBaseName,
renameIfAllowedType,
parentItemID,
saveOptions: {
notifierQueue
Expand All @@ -3298,7 +3294,36 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
// Otherwise file, so fall through
}

file = file.path;

// Rename file if it's an allowed type
let fileBaseName = false;
if (renameIfAllowedType) {
fileBaseName = yield Zotero.Attachments.getRenamedFileBaseNameIfAllowedType(
parentItem, file
);
}

if (dropEffect == 'link') {
// Rename linked file, with unique suffix if necessary
try {
if (fileBaseName) {
let ext = Zotero.File.getExtension(file);
let newName = yield Zotero.File.rename(
file,
fileBaseName + (ext ? '.' + ext : ''),
{
unique: true
}
);
// Update path in case the name was changed to be unique
file = OS.Path.join(OS.Path.dirname(file), newName);
}
}
catch (e) {
Zotero.logError(e);
}

yield Zotero.Attachments.linkFromFile({
file,
parentItemID,
Expand All @@ -3309,9 +3334,9 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
});
}
else {
if (file.leafName.endsWith(".lnk")) {
if (file.endsWith(".lnk")) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file.path);
win.ZoteroPane.displayCannotAddShortcutMessage(file);
continue;
}

Expand All @@ -3328,10 +3353,10 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
// If moving, delete original file
if (dragData.dropEffect == 'move') {
try {
file.remove(false);
yield OS.File.remove(file);
}
catch (e) {
Components.utils.reportError("Error deleting original file " + file.path + " after drag");
Zotero.logError("Error deleting original file " + file + " after drag");
}
}
}
Expand Down
52 changes: 35 additions & 17 deletions chrome/content/zotero/xpcom/recognizePDF.js
Expand Up @@ -241,30 +241,48 @@ Zotero.RecognizePDF = new function () {
* @return {Promise}
*/
async function _processItem(itemID) {
let item = await Zotero.Items.getAsync(itemID);
let attachment = await Zotero.Items.getAsync(itemID);

if (!item || item.parentItemID) throw new Zotero.Exception.Alert('recognizePDF.fileNotFound');
if (!attachment || attachment.parentItemID) {
throw new Zotero.Exception.Alert('recognizePDF.error');
}

let newItem = await _recognize(item);
let parentItem = await _recognize(attachment);
if (!parentItem) {
return null;
}

if (newItem) {
// put new item in same collections as the old one
let itemCollections = item.getCollections();
await Zotero.DB.executeTransaction(async function () {
for (let itemCollection of itemCollections) {
let collection = Zotero.Collections.get(itemCollection);
await collection.addItem(newItem.id);
// Put new item in same collections as the old one
let collections = attachment.getCollections();
await Zotero.DB.executeTransaction(async function () {
if (collections.length) {
for (let collection of collections) {
parentItem.addToCollection(collection.id);
}

// put old item as a child of the new item
item.parentID = newItem.id;
await item.save();
});
await parentItem.save();
}

return newItem
// Put old item as a child of the new item
attachment.parentID = parentItem.id;
await attachment.save();
});

// Rename attachment file to match new metadata
if (Zotero.Prefs.get('renameAttachmentFiles')) {
let path = attachment.getFilePath();
let ext = Zotero.File.getExtension(path);
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
let newName = fileBaseName + (ext ? '.' + ext : '');
let result = await attachment.renameAttachmentFile(newName, false, true);
if (result !== true) {
throw new Error("Error renaming " + path);
}
// Rename attachment title
attachment.setField('title', newName);
await attachment.saveTx();
}

return null;
return parentItem;
}

/**
Expand Down
102 changes: 71 additions & 31 deletions chrome/content/zotero/zoteroPane.js
Expand Up @@ -3678,41 +3678,81 @@ var ZoteroPane = new function()
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpenMultiple);
fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
fp.appendFilters(nsIFilePicker.filterAll);

if(fp.show() == nsIFilePicker.returnOK)
{
if (!parentItemID) {
var collection = this.getSelectedCollection(true);
}

var files = fp.files;
while (files.hasMoreElements()){
var file = files.getNext();
file.QueryInterface(Components.interfaces.nsILocalFile);
var attachmentID;
if (link) {
yield Zotero.Attachments.linkFromFile({
file: file,
parentItemID: parentItemID,
collections: collection ? [collection] : undefined
});
if (fp.show() != nsIFilePicker.returnOK) {
return;
}

var enumerator = fp.files;
var files = [];
while (enumerator.hasMoreElements()) {
let file = enumerator.getNext();
file.QueryInterface(Components.interfaces.nsIFile);
files.push(file.path);
}

var collection;
var fileBaseName;
if (parentItemID) {
// If only one item is being added, automatic renaming is enabled, and the parent item
// doesn't have any other non-HTML file attachments, rename the file.
// This should be kept in sync with itemTreeView::drop().
if (files.length == 1 && Zotero.Prefs.get('renameAttachmentFiles.automatic')) {
let parentItem = Zotero.Items.get(parentItemID);
if (!parentItem.numNonHTMLFileAttachments()) {
fileBaseName = await Zotero.Attachments.getRenamedFileBaseNameIfAllowedType(
parentItem, files[0]
);
}
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;
}
}
// If not adding to an item, add to the current collection
else {
collection = this.getSelectedCollection(true);
}

for (let file of files) {
if (link) {
// Rename linked file, with unique suffix if necessary
try {
if (fileBaseName) {
let ext = Zotero.File.getExtension(file);
let newName = yield Zotero.File.rename(
file,
fileBaseName + (ext ? '.' + ext : ''),
{
unique: true
}
);
// Update path in case the name was changed to be unique
file = OS.Path.join(OS.Path.dirname(file), newName);
}
yield Zotero.Attachments.importFromFile({
file: file,
libraryID: libraryID,
parentItemID: parentItemID,
collections: collection ? [collection] : undefined
});
}
catch (e) {
Zotero.logError(e);
}

let item = yield Zotero.Attachments.linkFromFile({
file,
parentItemID,
collections: collection ? [collection] : undefined
});
}
else {
if (file.endsWith(".lnk")) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotAddShortcutMessage(file);
continue;
}

yield Zotero.Attachments.importFromFile({
file,
libraryID,
fileBaseName,
parentItemID,
collections: collection ? [collection] : undefined
});
}
}
});
Expand Down
1 change: 1 addition & 0 deletions chrome/locale/en-US/zotero/preferences.dtd
Expand Up @@ -27,6 +27,7 @@
<!ENTITY zotero.preferences.parseRISRefer "Use Zotero for downloaded BibTeX/RIS/Refer files">
<!ENTITY zotero.preferences.automaticSnapshots "Automatically take snapshots when creating items from web pages">
<!ENTITY zotero.preferences.downloadAssociatedFiles "Automatically attach associated PDFs and other files when saving items">
<!ENTITY zotero.preferences.renameAttachmentFiles "Automatically rename attachment files using parent metadata">
<!ENTITY zotero.preferences.automaticTags "Automatically tag items with keywords and subject headings">
<!ENTITY zotero.preferences.trashAutoEmptyDaysPre "Automatically remove items in the trash deleted more than">
<!ENTITY zotero.preferences.trashAutoEmptyDaysPost "days ago">
Expand Down

0 comments on commit f8b41c9

Please sign in to comment.