mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 11:39:01 +01:00 
			
		
		
		
	unify .setContent() .getContent() handling across notes, revisions, attachments
This commit is contained in:
		
							parent
							
								
									bb45c67e60
								
							
						
					
					
						commit
						2b84f1be00
					
				| @ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS "attachments" | ||||
|     title         TEXT not null, | ||||
|     isProtected    INT  not null DEFAULT 0, | ||||
|     blobId    TEXT not null, | ||||
|     utcDateScheduledForDeletionSince TEXT DEFAULT NULL, | ||||
|     utcDateModified TEXT not null, | ||||
|     isDeleted    INT  not null, | ||||
|     deleteId    TEXT DEFAULT NULL); | ||||
|  | ||||
| @ -7,6 +7,7 @@ const eventService = require("../../services/events"); | ||||
| const dateUtils = require("../../services/date_utils"); | ||||
| const cls = require("../../services/cls"); | ||||
| const log = require("../../services/log"); | ||||
| const protectedSessionService = require("../../services/protected_session.js"); | ||||
| 
 | ||||
| let becca = null; | ||||
| 
 | ||||
| @ -118,6 +119,113 @@ class AbstractBeccaEntity { | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** @protected */ | ||||
|     _isHot() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** @protected */ | ||||
|     _setContent(content) { | ||||
|         if (content === null || content === undefined) { | ||||
|             throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`); | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             content = content.toString(); | ||||
|         } | ||||
|         else { | ||||
|             content = Buffer.isBuffer(content) ? content : Buffer.from(content); | ||||
|         } | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.encrypt(content); | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot update content of blob since we're out of protected session.`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         sql.transactional(() => { | ||||
|             let newBlobId = this._saveBlob(content); | ||||
| 
 | ||||
|             if (newBlobId !== this.blobId) { | ||||
|                 this.blobId = newBlobId; | ||||
|                 this.save(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** @protected */ | ||||
|     _saveBlob(content) { | ||||
|         let newBlobId; | ||||
|         let blobNeedsInsert; | ||||
| 
 | ||||
|         if (this._isHot()) { | ||||
|             newBlobId = this.blobId || utils.randomBlobId(); | ||||
|             blobNeedsInsert = true; | ||||
|         } else { | ||||
|             newBlobId = utils.hashedBlobId(content); | ||||
|             blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]); | ||||
|         } | ||||
| 
 | ||||
|         if (blobNeedsInsert) { | ||||
|             const pojo = { | ||||
|                 blobId: newBlobId, | ||||
|                 content: content, | ||||
|                 dateModified: dateUtils.localNowDateTime(), | ||||
|                 utcDateModified: dateUtils.utcNowDateTime() | ||||
|             }; | ||||
| 
 | ||||
|             sql.upsert("blobs", "blobId", pojo); | ||||
| 
 | ||||
|             const hash = utils.hash(`${newBlobId}|${pojo.content.toString()}`); | ||||
| 
 | ||||
|             entityChangesService.addEntityChange({ | ||||
|                 entityName: 'blobs', | ||||
|                 entityId: newBlobId, | ||||
|                 hash: hash, | ||||
|                 isErased: false, | ||||
|                 utcDateChanged: pojo.utcDateModified, | ||||
|                 isSynced: true | ||||
|             }); | ||||
| 
 | ||||
|             eventService.emit(eventService.ENTITY_CHANGED, { | ||||
|                 entityName: 'blobs', | ||||
|                 entity: this | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return newBlobId; | ||||
|     } | ||||
| 
 | ||||
|     /** @protected */ | ||||
|     _getContent() { | ||||
|         const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); | ||||
| 
 | ||||
|         if (!row) { | ||||
|             throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`); | ||||
|         } | ||||
| 
 | ||||
|         let content = row.content; | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = content === null ? null : protectedSessionService.decrypt(content); | ||||
|             } else { | ||||
|                 content = ""; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             return content === null | ||||
|                 ? "" | ||||
|                 : content.toString("UTF-8"); | ||||
|         } else { | ||||
|             return content; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Mark the entity as (soft) deleted. It will be completely erased later. | ||||
|      * | ||||
|  | ||||
| @ -17,7 +17,8 @@ const AbstractBeccaEntity = require("./abstract_becca_entity"); | ||||
| class BAttachment extends AbstractBeccaEntity { | ||||
|     static get entityName() { return "attachments"; } | ||||
|     static get primaryKeyName() { return "attachmentId"; } | ||||
|     static get hashedProperties() { return ["attachmentId", "parentId", "role", "mime", "title", "utcDateModified"]; } | ||||
|     static get hashedProperties() { return ["attachmentId", "parentId", "role", "mime", "title", "blobId", | ||||
|                                             "utcDateScheduledForDeletionSince", "utcDateModified"]; } | ||||
| 
 | ||||
|     constructor(row) { | ||||
|         super(); | ||||
| @ -32,7 +33,7 @@ class BAttachment extends AbstractBeccaEntity { | ||||
|             throw new Error("'title' must be given to initialize a Attachment entity"); | ||||
|         } | ||||
| 
 | ||||
|         /** @type {string} needs to be set at the initialization time since it's used in the .setContent() */ | ||||
|         /** @type {string} */ | ||||
|         this.attachmentId = row.attachmentId || `${this.noteId}_${this.name}`; // FIXME
 | ||||
|         /** @type {string} either noteId or noteRevisionId to which this attachment belongs */ | ||||
|         this.parentId = row.parentId; | ||||
| @ -45,6 +46,8 @@ class BAttachment extends AbstractBeccaEntity { | ||||
|         /** @type {boolean} */ | ||||
|         this.isProtected = !!row.isProtected; | ||||
|         /** @type {string} */ | ||||
|         this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince; | ||||
|         /** @type {string} */ | ||||
|         this.utcDateModified = row.utcDateModified; | ||||
|     } | ||||
| 
 | ||||
| @ -58,68 +61,12 @@ class BAttachment extends AbstractBeccaEntity { | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {*} */ | ||||
|     getContent(silentNotFoundError = false) { | ||||
|         const res = sql.getRow(`SELECT content FROM attachment_contents WHERE attachmentId = ?`, [this.attachmentId]); | ||||
| 
 | ||||
|         if (!res) { | ||||
|             if (silentNotFoundError) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot find note attachment content for attachmentId=${this.attachmentId}`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let content = res.content; | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.decrypt(content); | ||||
|             } | ||||
|             else { | ||||
|                 content = ""; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             return content === null | ||||
|                 ? "" | ||||
|                 : content.toString("UTF-8"); | ||||
|         } | ||||
|         else { | ||||
|             return content; | ||||
|         } | ||||
|     getContent() { | ||||
|         return this._getContent(); | ||||
|     } | ||||
| 
 | ||||
|     setContent(content) { | ||||
|         sql.transactional(() => { | ||||
|             this.save(); // also explicitly save attachment to update contentCheckSum
 | ||||
| 
 | ||||
|             const pojo = { | ||||
|                 attachmentId: this.attachmentId, | ||||
|                 content: content, | ||||
|                 utcDateModified: dateUtils.utcNowDateTime() | ||||
|             }; | ||||
| 
 | ||||
|             if (this.isProtected) { | ||||
|                 if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                     pojo.content = protectedSessionService.encrypt(pojo.content); | ||||
|                 } else { | ||||
|                     throw new Error(`Cannot update content of attachmentId=${this.attachmentId} since we're out of protected session.`); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             sql.upsert("attachment_contents", "attachmentId", pojo); | ||||
| 
 | ||||
|             entityChangesService.addEntityChange({ | ||||
|                 entityName: 'attachment_contents', | ||||
|                 entityId: this.attachmentId, | ||||
|                 hash: this.contentCheckSum, // FIXME
 | ||||
|                 isErased: false, | ||||
|                 utcDateChanged: pojo.utcDateModified, | ||||
|                 isSynced: true | ||||
|             }); | ||||
|         }); | ||||
|         this._setContent(content); | ||||
|     } | ||||
| 
 | ||||
|     calculateCheckSum(content) { | ||||
|  | ||||
| @ -208,37 +208,8 @@ class BNote extends AbstractBeccaEntity { | ||||
|      */ | ||||
| 
 | ||||
|     /** @returns {*} */ | ||||
|     getContent(silentNotFoundError = false) { | ||||
|         const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); | ||||
| 
 | ||||
|         if (!row) { | ||||
|             if (silentNotFoundError) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot find note content for noteId '${this.noteId}', blobId '${this.blobId}'.`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let content = row.content; | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = content === null ? null : protectedSessionService.decrypt(content); | ||||
|             } | ||||
|             else { | ||||
|                 content = ""; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             return content === null | ||||
|                 ? "" | ||||
|                 : content.toString("UTF-8"); | ||||
|         } | ||||
|         else { | ||||
|             return content; | ||||
|         } | ||||
|     getContent() { | ||||
|         return this._getContent(); | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {{contentLength, dateModified, utcDateModified}} */ | ||||
| @ -252,6 +223,29 @@ class BNote extends AbstractBeccaEntity { | ||||
|             WHERE blobId = ?`, [this.blobId]);
 | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {*} */ | ||||
|     getJsonContent() { | ||||
|         const content = this.getContent(); | ||||
| 
 | ||||
|         if (!content || !content.trim()) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return JSON.parse(content); | ||||
|     } | ||||
| 
 | ||||
|     _isHot() { | ||||
|         return ['text', 'code', 'relationMap', 'canvas', 'mermaid'].includes(this.type); | ||||
|     } | ||||
| 
 | ||||
|     setContent(content) { | ||||
|         this._setContent(content) | ||||
|     } | ||||
| 
 | ||||
|     setJsonContent(content) { | ||||
|         this.setContent(JSON.stringify(content, null, '\t')); | ||||
|     } | ||||
| 
 | ||||
|     get dateCreatedObj() { | ||||
|         return this.dateCreated === null ? null : dayjs(this.dateCreated); | ||||
|     } | ||||
| @ -268,90 +262,6 @@ class BNote extends AbstractBeccaEntity { | ||||
|         return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified); | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {*} */ | ||||
|     getJsonContent() { | ||||
|         const content = this.getContent(); | ||||
| 
 | ||||
|         if (!content || !content.trim()) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return JSON.parse(content); | ||||
|     } | ||||
| 
 | ||||
|     isHot() { | ||||
|         return ['text', 'code', 'relationMap', 'canvas', 'mermaid'].includes(this.type); | ||||
|     } | ||||
| 
 | ||||
|     setContent(content, ignoreMissingProtectedSession = false) { | ||||
|         if (content === null || content === undefined) { | ||||
|             throw new Error(`Cannot set null content to note '${this.noteId}'`); | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             content = content.toString(); | ||||
|         } | ||||
|         else { | ||||
|             content = Buffer.isBuffer(content) ? content : Buffer.from(content); | ||||
|         } | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.encrypt(content); | ||||
|             } | ||||
|             else if (!ignoreMissingProtectedSession) { | ||||
|                 throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let newBlobId; | ||||
|         let blobNeedsInsert; | ||||
| 
 | ||||
|         if (this.isHot()) { | ||||
|             newBlobId = this.blobId || utils.randomBlobId(); | ||||
|             blobNeedsInsert = true; | ||||
|         } else { | ||||
|             newBlobId = utils.hashedBlobId(content); | ||||
|             blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]); | ||||
|         } | ||||
| 
 | ||||
|         if (blobNeedsInsert) { | ||||
|             const pojo = { | ||||
|                 blobId: this.blobId, | ||||
|                 content: content, | ||||
|                 dateModified: dateUtils.localNowDateTime(), | ||||
|                 utcDateModified: dateUtils.utcNowDateTime() | ||||
|             }; | ||||
| 
 | ||||
|             sql.upsert("blobs", "blobId", pojo); | ||||
| 
 | ||||
|             const hash = utils.hash(`${this.blobId}|${pojo.content.toString()}`); | ||||
| 
 | ||||
|             entityChangesService.addEntityChange({ | ||||
|                 entityName: 'blobs', | ||||
|                 entityId: this.blobId, | ||||
|                 hash: hash, | ||||
|                 isErased: false, | ||||
|                 utcDateChanged: pojo.utcDateModified, | ||||
|                 isSynced: true | ||||
|             }); | ||||
| 
 | ||||
|             eventService.emit(eventService.ENTITY_CHANGED, { | ||||
|                 entityName: 'blobs', | ||||
|                 entity: this | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (newBlobId !== this.blobId) { | ||||
|             this.blobId = newBlobId; | ||||
|             this.save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     setJsonContent(content) { | ||||
|         this.setContent(JSON.stringify(content, null, '\t')); | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */ | ||||
|     isRoot() { | ||||
|         return this.noteId === 'root'; | ||||
|  | ||||
| @ -75,76 +75,12 @@ class BNoteRevision extends AbstractBeccaEntity { | ||||
|      */ | ||||
| 
 | ||||
|     /** @returns {*} */ | ||||
|     getContent(silentNotFoundError = false) { | ||||
|         const res = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); | ||||
| 
 | ||||
|         if (!res) { | ||||
|             if (silentNotFoundError) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot find note revision content for noteRevisionId '${this.noteRevisionId}', blobId '${this.blobId}'`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let content = res.content; | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.decrypt(content); | ||||
|             } | ||||
|             else { | ||||
|                 content = ""; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.isStringNote()) { | ||||
|             return content === null | ||||
|                 ? "" | ||||
|                 : content.toString("UTF-8"); | ||||
|         } | ||||
|         else { | ||||
|             return content; | ||||
|         } | ||||
|     getContent() { | ||||
|         return this._getContent(); | ||||
|     } | ||||
| 
 | ||||
|     setContent(content) { | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.encrypt(content); | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot update content of noteRevisionId '${this.noteRevisionId}' since we're out of protected session.`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.blobId = utils.hashedBlobId(content); | ||||
| 
 | ||||
|         const blobAlreadyExists = !!sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [this.blobId]); | ||||
| 
 | ||||
|         if (!blobAlreadyExists) { | ||||
|             const pojo = { | ||||
|                 blobId: this.blobId, | ||||
|                 content: content, | ||||
|                 dateModified: dateUtils.localNowDate(), | ||||
|                 utcDateModified: dateUtils.utcNowDateTime() | ||||
|             }; | ||||
| 
 | ||||
|             sql.insert("blobs", pojo); | ||||
| 
 | ||||
|             const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`); | ||||
| 
 | ||||
|             entityChangesService.addEntityChange({ | ||||
|                 entityName: 'blobs', | ||||
|                 entityId: this.blobId, | ||||
|                 hash: hash, | ||||
|                 isErased: false, | ||||
|                 utcDateChanged: this.getUtcDateChanged(), | ||||
|                 isSynced: true | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         this.save(); // saving this.blobId
 | ||||
|         this._setContent(content); | ||||
|     } | ||||
| 
 | ||||
|     beforeSaving() { | ||||
|  | ||||
| @ -8,7 +8,7 @@ function protectAttachments(note) { | ||||
|     for (const attachment of note.getAttachments()) { | ||||
|         if (note.isProtected !== attachment.isProtected) { | ||||
|             if (!protectedSession.isProtectedSessionAvailable()) { | ||||
|                 log.error("Protected session is not available to fix note attachments."); | ||||
|                 log.error("Protected session is not available to fix attachments."); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| @ -24,7 +24,7 @@ function protectAttachments(note) { | ||||
|                 attachment.save(); | ||||
|             } | ||||
|             catch (e) { | ||||
|                 log.error(`Could not un/protect note attachment ID = ${attachment.attachmentId}`); | ||||
|                 log.error(`Could not un/protect attachment ID = ${attachment.attachmentId}`); | ||||
| 
 | ||||
|                 throw e; | ||||
|             } | ||||
|  | ||||
| @ -231,9 +231,9 @@ class ConsistencyChecks { | ||||
| 
 | ||||
|                     this.reloadNeeded = false; | ||||
| 
 | ||||
|                     logFix(`Note attachment '${attachmentId}' has been deleted since it references missing note/revision '${parentId}'`); | ||||
|                     logFix(`Attachment '${attachmentId}' has been deleted since it references missing note/revision '${parentId}'`); | ||||
|                 } else { | ||||
|                     logError(`Note attachment '${attachmentId}' references missing note/revision '${parentId}'`); | ||||
|                     logError(`Attachment '${attachmentId}' references missing note/revision '${parentId}'`); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
| @ -358,9 +358,9 @@ class ConsistencyChecks { | ||||
| 
 | ||||
|                     this.reloadNeeded = false; | ||||
| 
 | ||||
|                     logFix(`Note attachment '${attachmentId}' has been deleted since associated note '${noteId}' is deleted.`); | ||||
|                     logFix(`Attachment '${attachmentId}' has been deleted since associated note '${noteId}' is deleted.`); | ||||
|                 } else { | ||||
|                     logError(`Note attachment '${attachmentId}' is not deleted even though associated note '${noteId}' is deleted.`) | ||||
|                     logError(`Attachment '${attachmentId}' is not deleted even though associated note '${noteId}' is deleted.`) | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam