Add import option for storing or linking files

This allows files in Mendeley imports to be stored and files in
RIS/BibTeX/etc. to be linked.

Closes #329
This commit is contained in:
Dan Stillman 2019-03-02 05:56:32 -05:00
parent 70a8bce739
commit 04779d8d1c
11 changed files with 181 additions and 22 deletions

View file

@ -296,6 +296,7 @@ var Zotero_File_Interface = new function() {
* @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker
* @param {Boolean} [options.addToLibraryRoot=false]
* @param {Boolean} [options.createNewCollection=true] - Put items in a new collection
* @param {Boolean} [options.linkFiles=false] - Link to files instead of storing them
* @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful
* for displaying progress in a different way. This also causes an error to be throw
* instead of shown in the main window.
@ -315,6 +316,7 @@ var Zotero_File_Interface = new function() {
var file = options.file ? Zotero.File.pathToFile(options.file) : null;
var createNewCollection = options.createNewCollection;
var addToLibraryRoot = options.addToLibraryRoot;
var linkFiles = options.linkFiles;
var onBeforeImport = options.onBeforeImport;
if (createNewCollection === undefined && !addToLibraryRoot) {
@ -359,6 +361,7 @@ var Zotero_File_Interface = new function() {
translation,
createNewCollection,
addToLibraryRoot,
linkFiles,
defaultNewCollectionPrefix,
onBeforeImport
});
@ -412,6 +415,7 @@ var Zotero_File_Interface = new function() {
var translation = options.translation;
var addToLibraryRoot = options.addToLibraryRoot;
var createNewCollection = options.createNewCollection;
var linkFiles = options.linkFiles;
var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix;
var onBeforeImport = options.onBeforeImport;
@ -518,7 +522,8 @@ var Zotero_File_Interface = new function() {
try {
yield translation.translate({
libraryID,
collections: importCollection ? [importCollection.id] : null
collections: importCollection ? [importCollection.id] : null,
linkFiles
});
} catch(e) {
if (!showProgressWindow) {

View file

@ -31,6 +31,17 @@ var Zotero_Import_Wizard = {
}
}
// Update labels
document.getElementById('file-handling-store').label = Zotero.getString(
'import.fileHandling.store',
Zotero.appName
);
document.getElementById('file-handling-link').label = Zotero.getString('import.fileHandling.link');
document.getElementById('file-handling-description').textContent = Zotero.getString(
'import.fileHandling.description',
Zotero.appName
);
Zotero.Translators.init(); // async
},
@ -160,20 +171,6 @@ var Zotero_Import_Wizard = {
},
onImportStart: async function () {
if (!this._file) {
let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path;
}
this._disableCancel();
this._wizard.canRewind = false;
this._wizard.canAdvance = false;
await this.doImport({
createNewCollection: document.getElementById('create-collection-checkbox').hasAttribute('checked')
});
},
onBeforeImport: async function (translation) {
// Unrecognized translator
if (!translation) {
@ -196,12 +193,22 @@ var Zotero_Import_Wizard = {
},
doImport: async function (options) {
onImportStart: async function () {
if (!this._file) {
let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path;
}
this._disableCancel();
this._wizard.canRewind = false;
this._wizard.canAdvance = false;
try {
let result = await Zotero_File_Interface.importFile({
file: this._file,
onBeforeImport: this.onBeforeImport.bind(this),
addToLibraryRoot: !options.createNewCollection
addToLibraryRoot: !document.getElementById('create-collection-checkbox')
.hasAttribute('checked'),
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1
});
// Cancelled by user or due to error

View file

@ -54,6 +54,16 @@
onpagerewound="return Zotero_Import_Wizard.goToStart()"
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
<vbox id="file-handling-options">
<label control="file-handling-radio" value="&zotero.import.fileHandling;"/>
<radiogroup id="file-handling-radio">
<radio id="file-handling-store" selected="true"/>
<radio id="file-handling-link"/>
</radiogroup>
<description id="file-handling-description"/>
</vbox>
</wizardpage>
<wizardpage pageid="page-progress"

View file

@ -6,6 +6,7 @@ Services.scriptloader.loadSubScript("chrome://zotero/content/include.js");
var Zotero_Import_Mendeley = function () {
this.createNewCollection = null;
this.linkFiles = null;
this.newItems = [];
this._db;
@ -39,7 +40,9 @@ Zotero_Import_Mendeley.prototype.getTranslators = async function () {
Zotero_Import_Mendeley.prototype.setTranslator = function () {};
Zotero_Import_Mendeley.prototype.translate = async function (options) {
Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
this._linkFiles = options.linkFiles;
if (true) {
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
}
@ -987,8 +990,8 @@ Zotero_Import_Mendeley.prototype._saveFilesAndAnnotations = async function (file
parentItemID,
file: realPath
};
// If file is in Mendeley downloads folder, import it
if (this._isDownloadedFile(path)) {
// If we're not set to link files or file is in Mendeley downloads folder, import it
if (!this._linkFiles || this._isDownloadedFile(path)) {
if (file.url) {
options.title = file.title;
options.url = file.url;

View file

@ -152,6 +152,8 @@ Zotero.Attachments = new function(){
* @param {nsIFile|String} options.file - File to add
* @param {Integer[]|String[]} [options.parentItemID] - Parent item to add item to
* @param {Integer[]} [options.collections] - Collection keys or ids to add new item to
* @param {String} [options.contentType] - Content type
* @param {String} [options.charset] - Character set
* @param {Object} [options.saveOptions] - Options to pass to Zotero.Item::save()
* @return {Promise<Zotero.Item>}
*/
@ -161,6 +163,8 @@ Zotero.Attachments = new function(){
var file = Zotero.File.pathToFile(options.file);
var parentItemID = options.parentItemID;
var collections = options.collections;
var contentType = options.contentType || (yield Zotero.MIME.getMIMETypeFromFile(file));
var charset = options.charset;
var saveOptions = options.saveOptions;
if (parentItemID && collections) {
@ -168,12 +172,12 @@ Zotero.Attachments = new function(){
}
var title = file.leafName;
var contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
var item = yield _addToDB({
file,
title,
linkMode: this.LINK_MODE_LINKED_FILE,
contentType,
charset,
parentItemID,
collections,
saveOptions

View file

@ -1282,6 +1282,7 @@ Zotero.Translate.Base.prototype = {
* or NULL for default library;
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
* @param {Boolean} [linkFiles=false] - Save linked files instead of stored files
* @returns {Promise} Promise resolved with saved items
* when translation complete
*/
@ -1321,6 +1322,7 @@ Zotero.Translate.Base.prototype = {
}
this._collections = options.collections;
this._saveAttachments = options.saveAttachments === undefined || options.saveAttachments;
this._linkFiles = options.linkFiles;
this._forceTagType = options.forceTagType;
this._saveOptions = options.saveOptions;
@ -2431,6 +2433,7 @@ Zotero.Translate.Import.prototype._prepareTranslation = Zotero.Promise.method(fu
collections: this._collections,
forceTagType: this._forceTagType,
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
linkFiles: this._linkFiles,
baseURI,
saveOptions: Object.assign(
{

View file

@ -31,6 +31,7 @@
* <li>libraryID - ID of library in which items should be saved</li>
* <li>collections - New collections to create (used during Import translation</li>
* <li>attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved</li>
* <li>linkFiles - Save attachments as linked files instead of stored files</li>
* <li>forceTagType - Force tags to specified tag type</li>
* <li>cookieSandbox - Cookie sandbox for attachment requests</li>
* <li>proxy - A proxy to deproxify item URLs</li>
@ -53,6 +54,7 @@ Zotero.Translate.ItemSaver = function(options) {
// If group filesEditable==false, don't save attachments
this.attachmentMode = Zotero.Libraries.get(this._libraryID).filesEditable ? options.attachmentMode :
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
this._linkFiles = options.linkFiles;
this._forceTagType = options.forceTagType;
this._referrer = options.referrer;
this._cookieSandbox = options.cookieSandbox;
@ -605,7 +607,24 @@ Zotero.Translate.ItemSaver.prototype = {
title: attachment.title || undefined,
collections: !parentItemID ? this._collections : undefined
});
} else {
}
else if (this._linkFiles
// Don't link if it's a path to the current storage directory
&& !Zotero.File.directoryContains(Zotero.DataDirectory.getSubdirectory('storage'), file.path)) {
attachment.linkMode = "linked_file";
newItem = yield Zotero.Attachments.linkFromFile({
file,
parentItemID,
collections: !parentItemID ? this._collections : undefined
});
if (attachment.title) {
newItem.setField("title", attachment.title);
}
if (attachment.url) {
newItem.setNote(attachment.url);
}
}
else {
if (attachment.url) {
attachment.linkMode = "imported_url";
newItem = yield Zotero.Attachments.importSnapshotFromFile({

View file

@ -203,6 +203,7 @@
<!ENTITY zotero.import.lastModified "Last Modified">
<!ENTITY zotero.import.size "Size">
<!ENTITY zotero.import.createCollection "Place imported collections and items into new collection">
<!ENTITY zotero.import.fileHandling "File Handling">
<!ENTITY zotero.exportOptions.title "Export…">
<!ENTITY zotero.exportOptions.format.label "Format:">

View file

@ -722,6 +722,10 @@ fileInterface.exportError = An error occurred while trying to export the selecte
fileInterface.importOPML = Import Feeds from OPML
fileInterface.OPMLFeedFilter = OPML Feed List
import.fileHandling.store = Copy files to the %S storage folder
import.fileHandling.link = Link to files in original location
import.fileHandling.description = Linked files cannot be synced by %S.
quickCopy.copyAs = Copy as %S
quickSearch.mode.titleCreatorYear = Title, Creator, Year

View file

@ -33,10 +33,32 @@ wizard[currentpageid="page-file-list"] .wizard-header {
margin-bottom: 6px;
}
#file-handling-options {
margin-top: 2em;
}
#file-handling-options > label {
font-size: 14px;
}
#file-handling-options radiogroup {
font-size: 13px;
margin-left: 1em;
}
#file-handling-options description {
margin-top: .6em;
font-size: 11px;
}
listbox, #result-description, #result-description-html {
font-size: 13px;
}
#result-description-html {
line-height: 1.5;
}
#result-description-html a {
text-decoration: underline;
}

View file

@ -505,6 +505,87 @@ describe("Zotero.Translate", function() {
assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
});
it("import translators should save linked-file attachments with linkFiles: true", async function () {
var testDir = getTestDataDirectory().path;
var file1 = OS.Path.join(testDir, 'test.pdf');
var file2 = OS.Path.join(testDir, 'test.html');
var file2URL = "http://example.com";
var json = [
{
itemType: "journalArticle",
title: "Parent Item",
attachments: [
{
title: "PDF",
mimeType: "application/pdf",
path: file1
},
{
title: "Snapshot",
mimeType: "text/html",
charset: "utf-8",
url: file2URL,
path: file2
}
]
}
];
var newItems = itemsArrayToObject(
await saveItemsThroughTranslator(
"import",
json,
{
linkFiles: true
}
)
);
var attachmentIDs = newItems["Parent Item"].getAttachments();
assert.lengthOf(attachmentIDs, 2);
var attachments = await Zotero.Items.getAsync(attachmentIDs);
assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE);
assert.equal(attachments[0].attachmentContentType, 'application/pdf');
assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE);
assert.equal(attachments[1].attachmentContentType, 'text/html');
assert.equal(attachments[1].attachmentCharset, 'utf-8');
assert.equal(attachments[1].getNote(), file2URL);
});
it("import translators shouldn't save linked-file attachment with linkFiles: true if path is within current storage directory", async function () {
var attachment = await importFileAttachment('test.png');
var path = attachment.getFilePath();
var json = [
{
itemType: "journalArticle",
title: "Parent Item",
attachments: [
{
title: "PDF",
mimeType: "application/pdf",
path
}
]
}
];
var newItems = itemsArrayToObject(
await saveItemsThroughTranslator(
"import",
json,
{
linkFiles: true
}
)
);
var attachmentIDs = newItems["Parent Item"].getAttachments();
assert.lengthOf(attachmentIDs, 1);
var attachments = await Zotero.Items.getAsync(attachmentIDs);
assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE);
var newPath = attachments[0].getFilePath();
assert.ok(newPath);
assert.notEqual(newPath, path);
});
it('web translators should set accessDate to current date', function* () {
let myItem = {
"itemType":"webpage",