mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 03:29:02 +01:00 
			
		
		
		
	copying attachments WIP
This commit is contained in:
		
							parent
							
								
									49fb913eab
								
							
						
					
					
						commit
						330e7ac08e
					
				| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| const sql = require("../services/sql"); | ||||
| const NoteSet = require("../services/search/note_set"); | ||||
| const BAttachment = require("./entities/battachment.js"); | ||||
| 
 | ||||
| /** | ||||
|  * Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca. | ||||
| @ -129,6 +130,13 @@ class Becca { | ||||
|         return row ? new BAttachment(row) : null; | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {BAttachment[]} */ | ||||
|     getAttachments(attachmentIds) { | ||||
|         const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
 | ||||
|         return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds) | ||||
|             .map(row => new BAttachment(row)); | ||||
|     } | ||||
| 
 | ||||
|     /** @returns {BOption|null} */ | ||||
|     getOption(name) { | ||||
|         return this.options[name]; | ||||
|  | ||||
| @ -148,9 +148,17 @@ class AbstractBeccaEntity { | ||||
|             content = Buffer.isBuffer(content) ? content : Buffer.from(content); | ||||
|         } | ||||
| 
 | ||||
|         let unencryptedContentForHashCalculation = content; | ||||
| 
 | ||||
|         if (this.isProtected) { | ||||
|             if (protectedSessionService.isProtectedSessionAvailable()) { | ||||
|                 content = protectedSessionService.encrypt(content); | ||||
| 
 | ||||
|                 // this is to make sure that the calculated hash/blobId is different for an encrypted note and decrypted
 | ||||
|                 const encryptedPrefixSuffix = "ThisIsEncryptedContent&^$#$1%&8*)(^%$5#@"; | ||||
|                 unencryptedContentForHashCalculation = Buffer.isBuffer(unencryptedContentForHashCalculation) | ||||
|                     ? Buffer.concat([Buffer.from(encryptedPrefixSuffix), unencryptedContentForHashCalculation, Buffer.from(encryptedPrefixSuffix)]) | ||||
|                     : `${encryptedPrefixSuffix}${unencryptedContentForHashCalculation}${encryptedPrefixSuffix}`; | ||||
|             } | ||||
|             else { | ||||
|                 throw new Error(`Cannot update content of blob since we're out of protected session.`); | ||||
| @ -158,7 +166,7 @@ class AbstractBeccaEntity { | ||||
|         } | ||||
| 
 | ||||
|         sql.transactional(() => { | ||||
|             let newBlobId = this._saveBlob(content, opts); | ||||
|             let newBlobId = this._saveBlob(content, unencryptedContentForHashCalculation, opts); | ||||
| 
 | ||||
|             if (newBlobId !== this.blobId || opts.forceSave) { | ||||
|                 this.blobId = newBlobId; | ||||
| @ -168,7 +176,7 @@ class AbstractBeccaEntity { | ||||
|     } | ||||
| 
 | ||||
|     /** @protected */ | ||||
|     _saveBlob(content, opts) { | ||||
|     _saveBlob(content, unencryptedContentForHashCalculation, opts) { | ||||
|         let newBlobId; | ||||
|         let blobNeedsInsert; | ||||
| 
 | ||||
| @ -176,7 +184,13 @@ class AbstractBeccaEntity { | ||||
|             newBlobId = this.blobId || utils.randomBlobId(); | ||||
|             blobNeedsInsert = true; | ||||
|         } else { | ||||
|             newBlobId = utils.hashedBlobId(content); | ||||
|             /* | ||||
|              * We're using the unencrypted blob for the hash calculation, because otherwise the random IV would | ||||
|              * cause every content blob to be unique which would balloon the database size (esp. with revisioning). | ||||
|              * This has minor security implications (it's easy to infer that given content is shared between different | ||||
|              * notes/attachments, but the trade-off comes out clearly positive. | ||||
|              */ | ||||
|             newBlobId = utils.hashedBlobId(unencryptedContentForHashCalculation); | ||||
|             blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -67,8 +67,7 @@ class BAttachment extends AbstractBeccaEntity { | ||||
|             mime: this.mime, | ||||
|             title: this.title, | ||||
|             blobId: this.blobId, | ||||
|             isProtected: this.isProtected, | ||||
|             utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince | ||||
|             isProtected: this.isProtected | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1580,6 +1580,10 @@ class BNote extends AbstractBeccaEntity { | ||||
|             noteRevision.save(); // to generate noteRevisionId which is then used to save attachments
 | ||||
| 
 | ||||
|             for (const noteAttachment of this.getAttachments()) { | ||||
|                 if (noteAttachment.utcDateScheduledForErasureSince) { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 const revisionAttachment = noteAttachment.copy(); | ||||
|                 revisionAttachment.parentId = noteRevision.noteRevisionId; | ||||
|                 revisionAttachment.setContent(noteAttachment.getContent(), { | ||||
|  | ||||
| @ -44,8 +44,6 @@ export default class AbstractTextTypeWidget extends TypeWidget { | ||||
|     } | ||||
| 
 | ||||
|     async parseFromImage($img) { | ||||
|         let noteId, viewScope; | ||||
| 
 | ||||
|         const imgSrc = $img.prop("src"); | ||||
| 
 | ||||
|         const imageNoteMatch = imgSrc.match(/\/api\/images\/([A-Za-z0-9_]+)\//); | ||||
|  | ||||
| @ -24,8 +24,8 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget { | ||||
|         this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds"); | ||||
|         this.$eraseUnusedAttachmentsAfterTimeInSeconds.on('change', () => this.updateOption('eraseUnusedImageAttachmentsAfterSeconds', this.$eraseUnusedAttachmentsAfterTimeInSeconds.val())); | ||||
| 
 | ||||
|         this.$eraseDeletedNotesButton = this.$widget.find(".erase-unused-attachments-now-button"); | ||||
|         this.$eraseDeletedNotesButton.on('click', () => { | ||||
|         this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button"); | ||||
|         this.$eraseUnusedAttachmentsNowButton.on('click', () => { | ||||
|             server.post('notes/erase-unused-attachments-now').then(() => { | ||||
|                 toastService.showMessage("Unused image attachments have been erased."); | ||||
|             }); | ||||
|  | ||||
| @ -346,7 +346,9 @@ function checkImageAttachments(note, content) { | ||||
|         foundAttachmentIds.add(match[1]); | ||||
|     } | ||||
| 
 | ||||
|     for (const attachment of note.getAttachmentByRole('image')) { | ||||
|     const imageAttachments = note.getAttachmentByRole('image'); | ||||
| 
 | ||||
|     for (const attachment of imageAttachments) { | ||||
|         const imageInContent = foundAttachmentIds.has(attachment.attachmentId); | ||||
| 
 | ||||
|         if (attachment.utcDateScheduledForErasureSince && imageInContent) { | ||||
| @ -357,6 +359,20 @@ function checkImageAttachments(note, content) { | ||||
|             attachment.save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const existingAttachmentIds = new Set(imageAttachments.map(att => att.attachmentId)); | ||||
|     const unknownAttachmentIds = Array.from(foundAttachmentIds).filter(foundAttId => !existingAttachmentIds.has(foundAttId)); | ||||
| 
 | ||||
|     for (const unknownAttachment of becca.getAttachments(unknownAttachmentIds)) { | ||||
|         // the attachment belongs to a different note (was copy pasted), we need to make a copy for this note.
 | ||||
|         const newAttachment = unknownAttachment.copy(); | ||||
|         newAttachment.parentId = note.noteId; | ||||
|         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); | ||||
| 
 | ||||
|         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); | ||||
|     } | ||||
| 
 | ||||
|     return content; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -581,7 +597,7 @@ function saveLinks(note, content) { | ||||
|         content = findInternalLinks(content, foundLinks); | ||||
|         content = findIncludeNoteLinks(content, foundLinks); | ||||
| 
 | ||||
|         checkImageAttachments(note, content); | ||||
|         content = checkImageAttachments(note, content); | ||||
|     } | ||||
|     else if (note.type === 'relationMap') { | ||||
|         findRelationMapLinks(content, foundLinks); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam