Switch to cache files for image annotations

Instead of embedded-image attachments

Create with Zotero.Annotations.saveCacheImage({ libraryID, key }, blob)
This commit is contained in:
Dan Stillman 2021-01-20 07:12:59 -05:00
parent 8c464a7a8b
commit 226fc90308
4 changed files with 108 additions and 34 deletions

View file

@ -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;

View file

@ -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 "

View file

@ -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']) {

View file

@ -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));
});
});