From 34ecd77bd47d1df8c72462f657aa9fb358f16b88 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 11 Apr 2023 23:24:39 +0200 Subject: [PATCH] note revision save also saves attachments --- src/becca/entities/abstract_becca_entity.js | 16 ++++-- src/becca/entities/battachment.js | 14 +++++ src/becca/entities/bnote.js | 57 +++++++++++-------- .../widgets/type_widgets/attachment_detail.js | 2 +- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/becca/entities/abstract_becca_entity.js b/src/becca/entities/abstract_becca_entity.js index ee2064515..2934c7856 100644 --- a/src/becca/entities/abstract_becca_entity.js +++ b/src/becca/entities/abstract_becca_entity.js @@ -119,7 +119,14 @@ class AbstractBeccaEntity { return this; } - /** @protected */ + /** + * Hot entities keep stable blobId which is continuously updated and is not shared with other entities. + * Cold entities can still update its blob, but the blobId will change (and new blob will be created). + * Functionally this is the same, it's an optimization to avoid creating a new blob every second with auto saved + * text notes. + * + * @protected + */ _isHot() { return false; } @@ -128,6 +135,7 @@ class AbstractBeccaEntity { _setContent(content, opts = {}) { // client code asks to save entity even if blobId didn't change (something else was changed) opts.forceSave = !!opts.forceSave; + opts.forceCold = !!opts.forceCold; if (content === null || content === undefined) { throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`); @@ -150,7 +158,7 @@ class AbstractBeccaEntity { } sql.transactional(() => { - let newBlobId = this._saveBlob(content); + let newBlobId = this._saveBlob(content, opts); if (newBlobId !== this.blobId || opts.forceSave) { this.blobId = newBlobId; @@ -160,11 +168,11 @@ class AbstractBeccaEntity { } /** @protected */ - _saveBlob(content) { + _saveBlob(content, opts) { let newBlobId; let blobNeedsInsert; - if (this._isHot()) { + if (this._isHot() && !opts.forceCold) { newBlobId = this.blobId || utils.randomBlobId(); blobNeedsInsert = true; } else { diff --git a/src/becca/entities/battachment.js b/src/becca/entities/battachment.js index 963994ae9..09af5041b 100644 --- a/src/becca/entities/battachment.js +++ b/src/becca/entities/battachment.js @@ -55,6 +55,19 @@ class BAttachment extends AbstractBeccaEntity { this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince; } + /** @returns {BAttachment} */ + copy() { + return new BAttachment({ + parentId: this.parentId, + role: this.role, + mime: this.mime, + title: this.title, + blobId: this.blobId, + isProtected: this.isProtected, + utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince + }); + } + getNote() { return becca.notes[this.parentId]; } @@ -73,6 +86,7 @@ class BAttachment extends AbstractBeccaEntity { * @param content * @param {object} [opts] * @param {object} [opts.forceSave=false] - will also save this BAttachment entity + * @param {object} [opts.forceCold=false] - blob has to be saved as cold */ setContent(content, opts) { this._setContent(content, opts); diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index a16143406..7aa467a8c 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -241,6 +241,7 @@ class BNote extends AbstractBeccaEntity { * @param content * @param {object} [opts] * @param {object} [opts.forceSave=false] - will also save this BNote entity + * @param {object} [opts.forceCold=false] - blob has to be saved as cold */ setContent(content, opts) { this._setContent(content, opts); @@ -1521,35 +1522,43 @@ class BNote extends AbstractBeccaEntity { * @returns {BNoteRevision|null} */ saveNoteRevision() { - const content = this.getContent(); + return sql.transactional(() => { + const content = this.getContent(); + const contentMetadata = this.getContentMetadata(); - if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) { - return null; - } + const noteRevision = new BNoteRevision({ + noteId: this.noteId, + // title and text should be decrypted now + title: this.title, + type: this.type, + mime: this.mime, + isProtected: this.isProtected, + utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified + ? this.utcDateModified + : contentMetadata.utcDateModified, + utcDateCreated: dateUtils.utcNowDateTime(), + utcDateModified: dateUtils.utcNowDateTime(), + dateLastEdited: this.dateModified > contentMetadata.dateModified + ? this.dateModified + : contentMetadata.dateModified, + dateCreated: dateUtils.localNowDateTime() + }, true); - const contentMetadata = this.getContentMetadata(); + noteRevision.setContent(content, { forceSave: true }); - const noteRevision = new BNoteRevision({ - noteId: this.noteId, - // title and text should be decrypted now - title: this.title, - type: this.type, - mime: this.mime, - isProtected: this.isProtected, - utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified - ? this.utcDateModified - : contentMetadata.utcDateModified, - utcDateCreated: dateUtils.utcNowDateTime(), - utcDateModified: dateUtils.utcNowDateTime(), - dateLastEdited: this.dateModified > contentMetadata.dateModified - ? this.dateModified - : contentMetadata.dateModified, - dateCreated: dateUtils.localNowDateTime() - }, true); + for (const noteAttachment of this.getAttachments()) { + const content = noteAttachment.getContent(); - noteRevision.setContent(content, { forceSave: true }); + const revisionAttachment = noteAttachment.copy(); + revisionAttachment.parentId = noteRevision.noteRevisionId; + revisionAttachment.setContent(content, { + forceSave: true, + forceCold: true + }); + } - return noteRevision; + return noteRevision; + }); } /** diff --git a/src/public/app/widgets/type_widgets/attachment_detail.js b/src/public/app/widgets/type_widgets/attachment_detail.js index 128aca43c..3e390c974 100644 --- a/src/public/app/widgets/type_widgets/attachment_detail.js +++ b/src/public/app/widgets/type_widgets/attachment_detail.js @@ -48,7 +48,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { async entitiesReloadedEvent({loadResults}) { const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId); - if (attachmentChange.isDeleted) { + if (attachmentChange?.isDeleted) { this.refresh(); // all other updates are handled within AttachmentDetailWidget } }