From 8a33645360f7ec97faa1575bc79a1e95df21293a Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 23 Jan 2023 16:57:28 +0100 Subject: [PATCH] migrating canvas etc. --- db/migrations/0213__note_attachments.sql | 4 +- ...0214__migrate_canvas_note_to_attachment.js | 39 ++++++++++++++++ src/becca/entities/bnote.js | 25 +++++++++++ src/becca/entities/bnote_attachment.js | 6 +++ src/services/app_info.js | 2 +- src/services/consistency_checks.js | 44 ++++++++++++++++++- 6 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 db/migrations/0214__migrate_canvas_note_to_attachment.js diff --git a/db/migrations/0213__note_attachments.sql b/db/migrations/0213__note_attachments.sql index fc910b5d0..b3bf9ec72 100644 --- a/db/migrations/0213__note_attachments.sql +++ b/db/migrations/0213__note_attachments.sql @@ -15,5 +15,5 @@ CREATE TABLE IF NOT EXISTS "note_attachment_contents" (`noteAttachmentId` TEXT N CREATE INDEX IDX_note_attachments_name on note_attachments (name); -CREATE INDEX IDX_note_attachments_noteId - on note_attachments (noteId); +CREATE UNIQUE INDEX IDX_note_attachments_noteId_name + on note_attachments (noteId, name); diff --git a/db/migrations/0214__migrate_canvas_note_to_attachment.js b/db/migrations/0214__migrate_canvas_note_to_attachment.js new file mode 100644 index 000000000..82ba820fd --- /dev/null +++ b/db/migrations/0214__migrate_canvas_note_to_attachment.js @@ -0,0 +1,39 @@ +module.exports = async () => { + const cls = require("../../src/services/cls"); + const beccaLoader = require("../../src/becca/becca_loader"); + const becca = require("../../src/becca/becca"); + const log = require("../../src/services/log"); + + await cls.init(async () => { + beccaLoader.load(); + + for (const note of Object.values(becca.notes)) { + if (note.type !== 'canvas') { + continue; + } + + if (note.isProtected) { + // can't migrate protected notes, but that's not critical. + continue; + } + + const content = note.getContent(true); + let svg; + + try { + const payload = JSON.parse(content); + svg = payload?.svg; + + if (!svg) { + continue; + } + } + catch (e) { + log.info(`Could not create a note attachment for canvas "${note.noteId}" with error: ${e.message} ${e.stack}`); + continue; + } + + note.saveNoteAttachment('canvasSvg', 'image/svg+xml', svg); + } + }); +}; \ No newline at end of file diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index ff8b40371..7dc0cb92f 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -1116,6 +1116,12 @@ class BNote extends AbstractBeccaEntity { .map(row => new BNoteAttachment(row)); } + getNoteAttachmentByName(name) { + return sql.getRows("SELECT * FROM note_attachments WHERE noteId = ? AND name = ? AND isDeleted = 0", [this.noteId, name]) + .map(row => new BNoteAttachment(row)) + [0]; + } + /** * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) */ @@ -1429,6 +1435,25 @@ class BNote extends AbstractBeccaEntity { return noteRevision; } + /** + * @returns {BNoteAttachment} + */ + saveNoteAttachment(name, mime, content) { + this.getNoteAttachments() + + const noteAttachment = new BNoteAttachment({ + name, + mime, + isProtected: this.isProtected + }); + + noteAttachment.save(); + + noteAttachment.setContent(content); + + return noteAttachment; + } + beforeSaving() { super.beforeSaving(); diff --git a/src/becca/entities/bnote_attachment.js b/src/becca/entities/bnote_attachment.js index a68447dfd..b60476d29 100644 --- a/src/becca/entities/bnote_attachment.js +++ b/src/becca/entities/bnote_attachment.js @@ -110,6 +110,12 @@ class BNoteAttachment extends AbstractBeccaEntity { } beforeSaving() { + if (!this.name.match(/^[a-z0-9]+$/i)) { + throw new Error(`Name must be alphanumerical, "${this.name}" given.`); + } + + this.noteAttachmentId = `${this.noteId}_${this.name}`; + super.beforeSaving(); this.utcDateModified = dateUtils.utcNowDateTime(); diff --git a/src/services/app_info.js b/src/services/app_info.js index 15cc4691c..5b7582d61 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -4,7 +4,7 @@ const build = require('./build'); const packageJson = require('../../package'); const {TRILIUM_DATA_DIR} = require('./data_dir'); -const APP_DB_VERSION = 213; +const APP_DB_VERSION = 214; const SYNC_VERSION = 30; const CLIPPER_PROTOCOL_VERSION = "1.0"; diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index b778721ec..f327e34fa 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -198,11 +198,31 @@ class ConsistencyChecks { logError(`Relation '${attributeId}' references missing note '${noteId}'`) } }); + + this.findAndFixIssues(` + SELECT noteAttachmentId, note_attachments.noteId AS noteId + FROM note_attachments + LEFT JOIN notes USING (noteId) + WHERE notes.noteId IS NULL + AND note_attachments.isDeleted = 0`, + ({noteAttachmentId, noteId}) => { + if (this.autoFix) { + const noteAttachment = becca.getNoteAttachment(noteAttachmentId); + noteAttachment.markAsDeleted(); + + this.reloadNeeded = false; + + logFix(`Note attachment '${noteAttachmentId}' has been deleted since it references missing note '${noteId}'`); + } else { + logError(`Note attachment '${noteAttachmentId}' references missing note '${noteId}'`); + } + }); } findExistencyIssues() { - // principle for fixing inconsistencies is that if the note itself is deleted (isDeleted=true) then all related entities should be also deleted (branches, attributes) - // but if note is not deleted, then at least one branch should exist. + // principle for fixing inconsistencies is that if the note itself is deleted (isDeleted=true) then all related + // entities should be also deleted (branches, attributes), but if note is not deleted, + // then at least one branch should exist. // the order here is important - first we might need to delete inconsistent branches and after that // another check might create missing branch @@ -304,6 +324,26 @@ class ConsistencyChecks { logError(`Duplicate branches for note '${noteId}' and parent '${parentNoteId}'`); } }); + + this.findAndFixIssues(` + SELECT noteAttachmentId, + note_attachments.noteId AS noteId + FROM note_attachments + JOIN notes USING (noteId) + WHERE notes.isDeleted = 1 + AND note_attachments.isDeleted = 0`, + ({noteAttachmentId, noteId}) => { + if (this.autoFix) { + const noteAttachment = becca.getNoteAttachment(noteAttachmentId); + noteAttachment.markAsDeleted(); + + this.reloadNeeded = false; + + logFix(`Note attachment '${noteAttachmentId}' has been deleted since associated note '${noteId}' is deleted.`); + } else { + logError(`Note attachment '${noteAttachmentId}' is not deleted even though associated note '${noteId}' is deleted.`) + } + }); } findLogicIssues() {