Add Zotero.Item.prototype.moveToLibrary()

Move an item and its attachments to another library. Attachments are
removed as necessary if linked files or all files aren't supported in
the target library.
This commit is contained in:
Dan Stillman 2018-03-31 08:03:28 -04:00
parent bc141ce36b
commit 9e955bde99
3 changed files with 209 additions and 4 deletions

View file

@ -1115,13 +1115,75 @@ Zotero.Attachments = new function(){
/**
* Copy attachment item, including files, to another library
* Move attachment item, including file, to another library
*/
this.moveAttachmentToLibrary = async function (attachment, libraryID, parentItemID) {
if (attachment.libraryID == libraryID) {
throw new Error("Attachment is already in library " + libraryID);
}
Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;
}
if (parentItemID) {
newAttachment.parentID = parentItemID;
}
await newAttachment.save();
// Move files over if they exist
var oldDir;
var newDir;
if (newAttachment.isImportedAttachment()) {
oldDir = this.getStorageDirectory(attachment).path;
if (await OS.File.exists(oldDir)) {
newDir = this.getStorageDirectory(newAttachment).path;
// Target directory shouldn't exist, but remove it if it does
//
// Testing for directories in OS.File, used by removeDir(), is broken on Travis,
// so use nsIFile
if (Zotero.automatedTest) {
let nsIFile = Zotero.File.pathToFile(newDir);
if (nsIFile.exists()) {
nsIFile.remove(true);
}
}
else {
await OS.File.removeDir(newDir, { ignoreAbsent: true });
}
await OS.File.move(oldDir, newDir);
}
}
try {
await attachment.erase();
}
catch (e) {
// Move files back if old item can't be deleted
if (newAttachment.isImportedAttachment()) {
try {
await OS.File.move(newDir, oldDir);
}
catch (e) {
Zotero.logError(e);
}
}
throw e;
}
return newAttachment.id;
};
/**
* Copy attachment item, including file, to another library
*/
this.copyAttachmentToLibrary = Zotero.Promise.coroutine(function* (attachment, libraryID, parentItemID) {
var linkMode = attachment.attachmentLinkMode;
if (attachment.libraryID == libraryID) {
throw ("Attachment is already in library " + libraryID);
throw new Error("Attachment is already in library " + libraryID);
}
Zotero.DB.requireTransaction();

View file

@ -3989,6 +3989,89 @@ Zotero.Item.prototype.clone = function (libraryID, options = {}) {
}
/**
* @param {Zotero.Item} item
* @param {Integer} libraryID
* @return {Zotero.Item} - New item
*/
Zotero.Item.prototype.moveToLibrary = async function (libraryID, onSkippedAttachment) {
if (!this.isEditable) {
throw new Error("Can't move item in read-only library");
}
var library = Zotero.Libraries.get(libraryID);
Zotero.debug("Moving item to " + library.name);
if (!library.editable) {
throw new Error("Can't move item to read-only library");
}
var filesEditable = library.filesEditable;
var allowsLinkedFiles = library.allowsLinkedFiles;
var newItem = await Zotero.DB.executeTransaction(async function () {
// Create new clone item in target library
var newItem = this.clone(libraryID);
var newItemID = await newItem.save({
skipSelect: true
});
if (this.isNote()) {
// Delete old item
await this.erase();
return newItem;
}
// For regular items, add child items
// Child notes
var noteIDs = this.getNotes();
var notes = Zotero.Items.get(noteIDs);
for (let note of notes) {
let newNote = note.clone(libraryID);
newNote.parentID = newItemID;
await newNote.save({
skipSelect: true
});
}
// Child attachments
var attachmentIDs = this.getAttachments();
var attachments = Zotero.Items.get(attachmentIDs);
for (let attachment of attachments) {
let linkMode = attachment.attachmentLinkMode;
// Skip linked files if not allowed in destination
if (!allowsLinkedFiles && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
Zotero.debug("Target library doesn't support linked files -- skipping attachment");
if (onSkippedAttachment) {
await onSkippedAttachment(attachment);
}
continue;
}
// Skip files if not allowed in destination
if (!filesEditable && linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
Zotero.debug("Target library doesn't allow file editing -- skipping attachment");
if (onSkippedAttachment) {
await onSkippedAttachment(attachment);
}
continue;
}
await Zotero.Attachments.moveAttachmentToLibrary(
attachment, libraryID, newItemID
);
}
return newItem;
}.bind(this));
// Delete old item. Do this outside of a transaction so we don't leave stranded files
// in the target library if deleting fails.
await this.eraseTx();
return newItem;
};
Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
Zotero.DB.requireTransaction();

View file

@ -1258,6 +1258,66 @@ describe("Zotero.Item", function () {
})
})
describe("#moveToLibrary()", function () {
it("should move items from My Library to a filesEditable group", async function () {
var group = await createGroup();
var item = await createDataObject('item');
var attachment1 = await importFileAttachment('test.png', { parentID: item.id });
var file = getTestDataDirectory();
file.append('test.png');
var attachment2 = await Zotero.Attachments.linkFromFile({
file,
parentItemID: item.id
});
var note = await createDataObject('item', { itemType: 'note', parentID: item.id });
var originalIDs = [item.id, attachment1.id, attachment2.id, note.id];
var originalAttachmentFile = attachment1.getFilePath();
var originalAttachmentHash = await attachment1.attachmentHash
assert.isTrue(await OS.File.exists(originalAttachmentFile));
var newItem = await item.moveToLibrary(group.libraryID);
// Old items and file should be gone
assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id)));
assert.isFalse(await OS.File.exists(originalAttachmentFile));
// New items and stored file should exist; linked file should be gone
assert.equal(newItem.libraryID, group.libraryID);
assert.lengthOf(newItem.getAttachments(), 1);
var newAttachment = Zotero.Items.get(newItem.getAttachments()[0]);
assert.equal(await newAttachment.attachmentHash, originalAttachmentHash);
assert.lengthOf(newItem.getNotes(), 1);
});
it("should move items from My Library to a non-filesEditable group", async function () {
var group = await createGroup({
filesEditable: false
});
var item = await createDataObject('item');
var attachment = await importFileAttachment('test.png', { parentID: item.id });
var originalIDs = [item.id, attachment.id];
var originalAttachmentFile = attachment.getFilePath();
var originalAttachmentHash = await attachment.attachmentHash
assert.isTrue(await OS.File.exists(originalAttachmentFile));
var newItem = await item.moveToLibrary(group.libraryID);
// Old items and file should be gone
assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id)));
assert.isFalse(await OS.File.exists(originalAttachmentFile));
// Parent should exist, but attachment should not
assert.equal(newItem.libraryID, group.libraryID);
assert.lengthOf(newItem.getAttachments(), 0);
});
});
describe("#toJSON()", function () {
describe("default mode", function () {
it("should output only fields with values", function* () {