diff --git a/chrome/content/zotero/xpcom/annotations.js b/chrome/content/zotero/xpcom/annotations.js index da1f5609e3..127543459b 100644 --- a/chrome/content/zotero/xpcom/annotations.js +++ b/chrome/content/zotero/xpcom/annotations.js @@ -32,8 +32,75 @@ Zotero.Annotations = new function () { Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 }); + this.getCacheImagePath = function ({ libraryID, key }) { + var file = this._getLibraryCacheDirectory(libraryID); + return OS.Path.join(file, key + '.png'); + }; + + + this.saveCacheImage = async function ({ libraryID, key }, blob) { + var item = await Zotero.Items.getByLibraryAndKey(libraryID, key); + if (!item) { + throw new Error(`Item not found`); + } + if (item.itemType != 'annotation' || item.annotationType != 'image') { + throw new Error("Item must be an image annotation item"); + } + + var cacheDir = Zotero.DataDirectory.getSubdirectory('cache', true); + var file = this._getLibraryCacheDirectory(item.libraryID); + await Zotero.File.createDirectoryIfMissingAsync(file, { from: cacheDir }); + + file = OS.Path.join(file, item.key + '.png'); + await Zotero.File.putContentsAsync(file, blob); + await Zotero.File.setNormalFilePermissions(file); + + return file; + }; + + + this.removeCacheImage = async function ({ libraryID, key }) { + var path = this.getCacheImagePath({ libraryID, key }); + await OS.File.remove(path, { ignoreAbsent: true }); + }; + + + /** + * Remove cache files that are no longer in use + */ + this.removeOrphanedCacheFiles = async function () { + // TODO + }; + + + /** + * Remove all cache files for a given library + */ + this.removeLibraryCacheFiles = async function (libraryID) { + var path = this._getLibraryCacheDirectory(libraryID); + await OS.File.removeDir(path, { ignoreAbsent: true, ignorePermissions: true }); + }; + + + this._getLibraryCacheDirectory = function (libraryID) { + var parts = [Zotero.DataDirectory.getSubdirectory('cache')]; + var library = Zotero.Libraries.get(libraryID); + if (library.libraryType == 'user') { + parts.push('library'); + } + else if (library.libraryType == 'group') { + parts.push('groups', library.groupID); + } + else { + throw new Error(`Unexpected library type '${library.libraryType}'`); + } + return OS.Path.join(...parts); + }; + + this.toJSON = async function (item) { var o = {}; + o.libraryID = item.libraryID; o.key = item.key; o.type = item.annotationType; o.isAuthor = !item.createdByUserID || item.createdByUserID == Zotero.Users.getCurrentUserID(); @@ -44,12 +111,9 @@ Zotero.Annotations = new function () { o.text = item.annotationText; } else if (o.type == 'image') { - var attachments = item.getAttachments(); - if (attachments.length) { - let imageAttachment = Zotero.Items.get(attachments[0]); - if (imageAttachment) { - o.image = await imageAttachment.attachmentDataURI; - } + let file = this.getCacheImagePath(item); + if (await OS.File.exists(file)) { + o.image = await Zotero.File.generateDataURI(file, 'image/png'); } } o.comment = item.annotationComment; diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 29b8f10cfa..9000ea67eb 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -4530,6 +4530,12 @@ Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) { // Zotero.Sync.EventListeners.ChangeListener needs to know if this was a storage file env.notifierData[this.id].storageDeleteLog = this.isStoredFileAttachment(); } + // Delete cached file for image annotation + else if (this.isAnnotation()) { + if (this.isImageAnnotation()) { + yield Zotero.Annotations.removeCacheImage(this); + } + } // Regular item else { let sql = "SELECT itemID FROM itemNotes WHERE parentItemID=?1 UNION " diff --git a/test/tests/annotationsTest.js b/test/tests/annotationsTest.js index ef35116e70..07ba90de42 100644 --- a/test/tests/annotationsTest.js +++ b/test/tests/annotationsTest.js @@ -1,5 +1,6 @@ describe("Zotero.Annotations", function() { var exampleHighlight = { + "libraryID": null, "key": "92JLMCVT", "type": "highlight", "isAuthor": true, @@ -32,6 +33,7 @@ describe("Zotero.Annotations", function() { var exampleHighlightAlt = jsonPositionToString(exampleHighlight); var exampleNote = { + "libraryID": null, "key": "5TKU34XX", "type": "note", "isAuthor": true, @@ -50,6 +52,7 @@ describe("Zotero.Annotations", function() { var exampleNoteAlt = jsonPositionToString(exampleNote); var exampleImage = { + "libraryID": null, "key": "QD32MQJF", "type": "image", "isAuthor": true, @@ -71,6 +74,7 @@ describe("Zotero.Annotations", function() { var exampleImageAlt = jsonPositionToString(exampleImage); var exampleGroupHighlight = { + "libraryID": null, "key": "PE57YAYH", "type": "highlight", "isAuthor": false, @@ -111,8 +115,12 @@ describe("Zotero.Annotations", function() { before(async function () { item = await createDataObject('item'); attachment = await importFileAttachment('test.pdf', { parentID: item.id }); + exampleHighlight.libraryID = item.libraryID; + exampleNote.libraryID = item.libraryID; + exampleImage.libraryID = item.libraryID; group = await getGroup(); + exampleGroupHighlight.libraryID = group.libraryID; groupItem = await createDataObject('item', { libraryID: group.libraryID }); groupAttachment = await importFileAttachment( 'test.pdf', @@ -195,10 +203,7 @@ describe("Zotero.Annotations", function() { array[i] = imageData.charCodeAt(i); } var blob = new Blob([array], { type: 'image/png' }); - var imageAttachment = await Zotero.Attachments.importEmbeddedImage({ - blob, - parentItemID: annotation.id - }); + var file = await Zotero.Annotations.saveCacheImage(annotation, blob); var json = await Zotero.Annotations.toJSON(annotation); @@ -275,7 +280,7 @@ describe("Zotero.Annotations", function() { it("should create an item from an image", async function () { var annotation = await Zotero.Annotations.saveFromJSON(attachment, exampleImage); - // Note: Image is created separately using Zotero.Attachments.importEmbeddedImage() + // Note: Image is created separately using Zotero.Annotations.saveCacheImage() assert.equal(annotation.key, exampleImage.key); for (let prop of ['comment', 'color', 'pageLabel', 'sortIndex', 'position']) { diff --git a/test/tests/itemTest.js b/test/tests/itemTest.js index f4a16efc2b..a76d94e59e 100644 --- a/test/tests/itemTest.js +++ b/test/tests/itemTest.js @@ -1294,35 +1294,15 @@ describe("Zotero.Item", function () { await annotation.saveTx(); var blob = new Blob([array], { type: 'image/png' }); - await Zotero.Attachments.importEmbeddedImage({ - blob, - parentItemID: annotation.id - }); + await Zotero.Annotations.saveCacheImage(annotation, blob); - var attachments = annotation.getAttachments(); - assert.lengthOf(attachments, 1); - var imageAttachment = Zotero.Items.get(attachments[0]); - var imagePath = await imageAttachment.getFilePathAsync(); + var imagePath = Zotero.Annotations.getCacheImagePath(annotation); assert.ok(imagePath); - assert.equal(OS.Path.basename(imagePath), 'image.png'); + assert.equal(OS.Path.basename(imagePath), annotation.key + '.png'); assert.equal( await Zotero.File.getBinaryContentsAsync(imagePath), imageData ); - assert.equal(imageAttachment.attachmentContentType, 'image/png'); - - var blob2 = await new Zotero.Promise((resolve) => { - var reader = new FileReader(); - reader.addEventListener("load", function () { - resolve(reader.result); - }, false); - reader.readAsDataURL(blob); - }); - - assert.equal( - await imageAttachment.attachmentDataURI, - blob2 - ); }); }); @@ -1555,6 +1535,25 @@ describe("Zotero.Item", function () { skipEditCheck: true }); }); + + it("should remove cached image for an annotation item", async function () { + var attachment = await importFileAttachment('test.pdf'); + var annotation = await createAnnotation('image', attachment); + + // Get Blob from file and attach it + var path = OS.Path.join(getTestDataDirectory().path, 'test.png'); + var imageData = await Zotero.File.getBinaryContentsAsync(path); + var array = new Uint8Array(imageData.length); + for (let i = 0; i < imageData.length; i++) { + array[i] = imageData.charCodeAt(i); + } + var blob = new Blob([array], { type: 'image/png' }); + var file = await Zotero.Annotations.saveCacheImage(annotation, blob); + + assert.isTrue(await OS.File.exists(file)); + await annotation.eraseTx(); + assert.isFalse(await OS.File.exists(file)); + }); });