mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
copying attachments WIP
This commit is contained in:
parent
49fb913eab
commit
330e7ac08e
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const sql = require("../services/sql");
|
const sql = require("../services/sql");
|
||||||
const NoteSet = require("../services/search/note_set");
|
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.
|
* 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;
|
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} */
|
/** @returns {BOption|null} */
|
||||||
getOption(name) {
|
getOption(name) {
|
||||||
return this.options[name];
|
return this.options[name];
|
||||||
|
@ -148,9 +148,17 @@ class AbstractBeccaEntity {
|
|||||||
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unencryptedContentForHashCalculation = content;
|
||||||
|
|
||||||
if (this.isProtected) {
|
if (this.isProtected) {
|
||||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||||
content = protectedSessionService.encrypt(content);
|
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 {
|
else {
|
||||||
throw new Error(`Cannot update content of blob since we're out of protected session.`);
|
throw new Error(`Cannot update content of blob since we're out of protected session.`);
|
||||||
@ -158,7 +166,7 @@ class AbstractBeccaEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
let newBlobId = this._saveBlob(content, opts);
|
let newBlobId = this._saveBlob(content, unencryptedContentForHashCalculation, opts);
|
||||||
|
|
||||||
if (newBlobId !== this.blobId || opts.forceSave) {
|
if (newBlobId !== this.blobId || opts.forceSave) {
|
||||||
this.blobId = newBlobId;
|
this.blobId = newBlobId;
|
||||||
@ -168,7 +176,7 @@ class AbstractBeccaEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @protected */
|
/** @protected */
|
||||||
_saveBlob(content, opts) {
|
_saveBlob(content, unencryptedContentForHashCalculation, opts) {
|
||||||
let newBlobId;
|
let newBlobId;
|
||||||
let blobNeedsInsert;
|
let blobNeedsInsert;
|
||||||
|
|
||||||
@ -176,7 +184,13 @@ class AbstractBeccaEntity {
|
|||||||
newBlobId = this.blobId || utils.randomBlobId();
|
newBlobId = this.blobId || utils.randomBlobId();
|
||||||
blobNeedsInsert = true;
|
blobNeedsInsert = true;
|
||||||
} else {
|
} 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]);
|
blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +67,7 @@ class BAttachment extends AbstractBeccaEntity {
|
|||||||
mime: this.mime,
|
mime: this.mime,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
blobId: this.blobId,
|
blobId: this.blobId,
|
||||||
isProtected: this.isProtected,
|
isProtected: this.isProtected
|
||||||
utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1580,6 +1580,10 @@ class BNote extends AbstractBeccaEntity {
|
|||||||
noteRevision.save(); // to generate noteRevisionId which is then used to save attachments
|
noteRevision.save(); // to generate noteRevisionId which is then used to save attachments
|
||||||
|
|
||||||
for (const noteAttachment of this.getAttachments()) {
|
for (const noteAttachment of this.getAttachments()) {
|
||||||
|
if (noteAttachment.utcDateScheduledForErasureSince) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const revisionAttachment = noteAttachment.copy();
|
const revisionAttachment = noteAttachment.copy();
|
||||||
revisionAttachment.parentId = noteRevision.noteRevisionId;
|
revisionAttachment.parentId = noteRevision.noteRevisionId;
|
||||||
revisionAttachment.setContent(noteAttachment.getContent(), {
|
revisionAttachment.setContent(noteAttachment.getContent(), {
|
||||||
|
@ -44,8 +44,6 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseFromImage($img) {
|
async parseFromImage($img) {
|
||||||
let noteId, viewScope;
|
|
||||||
|
|
||||||
const imgSrc = $img.prop("src");
|
const imgSrc = $img.prop("src");
|
||||||
|
|
||||||
const imageNoteMatch = imgSrc.match(/\/api\/images\/([A-Za-z0-9_]+)\//);
|
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 = this.$widget.find(".erase-unused-attachments-after-time-in-seconds");
|
||||||
this.$eraseUnusedAttachmentsAfterTimeInSeconds.on('change', () => this.updateOption('eraseUnusedImageAttachmentsAfterSeconds', this.$eraseUnusedAttachmentsAfterTimeInSeconds.val()));
|
this.$eraseUnusedAttachmentsAfterTimeInSeconds.on('change', () => this.updateOption('eraseUnusedImageAttachmentsAfterSeconds', this.$eraseUnusedAttachmentsAfterTimeInSeconds.val()));
|
||||||
|
|
||||||
this.$eraseDeletedNotesButton = this.$widget.find(".erase-unused-attachments-now-button");
|
this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button");
|
||||||
this.$eraseDeletedNotesButton.on('click', () => {
|
this.$eraseUnusedAttachmentsNowButton.on('click', () => {
|
||||||
server.post('notes/erase-unused-attachments-now').then(() => {
|
server.post('notes/erase-unused-attachments-now').then(() => {
|
||||||
toastService.showMessage("Unused image attachments have been erased.");
|
toastService.showMessage("Unused image attachments have been erased.");
|
||||||
});
|
});
|
||||||
|
@ -346,7 +346,9 @@ function checkImageAttachments(note, content) {
|
|||||||
foundAttachmentIds.add(match[1]);
|
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);
|
const imageInContent = foundAttachmentIds.has(attachment.attachmentId);
|
||||||
|
|
||||||
if (attachment.utcDateScheduledForErasureSince && imageInContent) {
|
if (attachment.utcDateScheduledForErasureSince && imageInContent) {
|
||||||
@ -357,6 +359,20 @@ function checkImageAttachments(note, content) {
|
|||||||
attachment.save();
|
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 = findInternalLinks(content, foundLinks);
|
||||||
content = findIncludeNoteLinks(content, foundLinks);
|
content = findIncludeNoteLinks(content, foundLinks);
|
||||||
|
|
||||||
checkImageAttachments(note, content);
|
content = checkImageAttachments(note, content);
|
||||||
}
|
}
|
||||||
else if (note.type === 'relationMap') {
|
else if (note.type === 'relationMap') {
|
||||||
findRelationMapLinks(content, foundLinks);
|
findRelationMapLinks(content, foundLinks);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user