diff --git a/db/migrations/0125__create_note_content_table.sql b/db/migrations/0125__create_note_content_table.sql index 53c38487f..1fbcf55f4 100644 --- a/db/migrations/0125__create_note_content_table.sql +++ b/db/migrations/0125__create_note_content_table.sql @@ -4,6 +4,8 @@ CREATE TABLE IF NOT EXISTS "note_contents" ( `isProtected` INT NOT NULL DEFAULT 0, `content` TEXT NULL DEFAULT NULL, `hash` TEXT DEFAULT "" NOT NULL, + `dateCreated` TEXT NOT NULL, + `dateModified` TEXT NOT NULL, PRIMARY KEY(`noteContentId`) ); diff --git a/src/entities/note.js b/src/entities/note.js index 566335c01..7d936ef22 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -678,7 +678,6 @@ class Note extends Entity { } } - delete pojo.jsonContent; delete pojo.isContentAvailable; delete pojo.__attributeCache; delete pojo.titleCipherText; diff --git a/src/entities/note_content.js b/src/entities/note_content.js index 904f5d9c1..bb934b5e4 100644 --- a/src/entities/note_content.js +++ b/src/entities/note_content.js @@ -3,6 +3,7 @@ const Entity = require('./entity'); const protectedSessionService = require('../services/protected_session'); const repository = require('../services/repository'); +const dateUtils = require('../services/date_utils'); /** * This represents a Note which is a central object in the Trilium Notes project. @@ -11,6 +12,8 @@ const repository = require('../services/repository'); * @property {string} noteId - reference to owning note * @property {boolean} isProtected - true if note content is protected * @property {blob} content - note content - e.g. HTML text for text notes, file payload for files + * @property {string} dateCreated + * @property {string} dateModified * * @extends Entity */ @@ -61,6 +64,18 @@ class NoteContent extends Entity { return await repository.getNote(this.noteId); } + beforeSaving() { + if (!this.dateCreated) { + this.dateCreated = dateUtils.nowDate(); + } + + super.beforeSaving(); + + if (this.isChanged) { + this.dateModified = dateUtils.nowDate(); + } + } + // cannot be static! updatePojo(pojo) { if (pojo.isProtected) { @@ -73,7 +88,6 @@ class NoteContent extends Entity { } } - delete pojo.jsonContent; delete pojo.isContentAvailable; delete pojo.contentCipherText; } diff --git a/src/routes/api/login.js b/src/routes/api/login.js index 22bdd67e2..d661b5b87 100644 --- a/src/routes/api/login.js +++ b/src/routes/api/login.js @@ -31,7 +31,7 @@ async function loginSync(req) { const syncVersion = req.body.syncVersion; if (syncVersion !== appInfo.syncVersion) { - return [400, { message: `Non-matching sync versions, local is version ${appInfo.syncVersion}, remote is ${syncVersion}` }]; + return [400, { message: `Non-matching sync versions, local is version ${appInfo.syncVersion}, remote is ${syncVersion}. It is recommended to run same version of Trilium on both sides of sync.` }]; } const documentSecret = await options.getOption('documentSecret'); diff --git a/src/services/sync.js b/src/services/sync.js index 3f3844228..c145a72c3 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -239,6 +239,7 @@ async function syncRequest(syncContext, method, requestPath, body) { const primaryKeys = { "notes": "noteId", + "note_contents": "noteContentId", "branches": "branchId", "note_revisions": "noteRevisionId", "recent_notes": "branchId", @@ -261,9 +262,10 @@ async function getEntityRow(entityName, entityId) { const entity = await sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); - if (entityName === 'notes' - && entity.content !== null - && (entity.type === 'file' || entity.type === 'image')) { + if (['note_contents', 'note_revisions'].includes(entityName) && entity.content !== null) { + if (typeof entity.content === 'string') { + entity.content = Buffer.from(entity.content, 'UTF-8'); + } entity.content = entity.content.toString("base64"); } diff --git a/src/services/sync_table.js b/src/services/sync_table.js index 3f7387296..df693606d 100644 --- a/src/services/sync_table.js +++ b/src/services/sync_table.js @@ -8,6 +8,10 @@ async function addNoteSync(noteId, sourceId) { await addEntitySync("notes", noteId, sourceId) } +async function addNoteContentSync(noteContentId, sourceId) { + await addEntitySync("note_contents", noteContentId, sourceId) +} + async function addBranchSync(branchId, sourceId) { await addEntitySync("branches", branchId, sourceId) } @@ -84,6 +88,7 @@ async function fillAllSyncRows() { await sql.execute("DELETE FROM sync"); await fillSyncRows("notes", "noteId"); + await fillSyncRows("note_contents", "noteContentId"); await fillSyncRows("branches", "branchId"); await fillSyncRows("note_revisions", "noteRevisionId"); await fillSyncRows("recent_notes", "branchId"); @@ -95,6 +100,7 @@ async function fillAllSyncRows() { module.exports = { addNoteSync, + addNoteContentSync, addBranchSync, addNoteReorderingSync, addNoteRevisionSync, diff --git a/src/services/sync_update.js b/src/services/sync_update.js index a27304097..ff439a7bd 100644 --- a/src/services/sync_update.js +++ b/src/services/sync_update.js @@ -10,6 +10,9 @@ async function updateEntity(sync, entity, sourceId) { if (entityName === 'notes') { await updateNote(entity, sourceId); } + else if (entityName === 'note_contents') { + await updateNoteContent(entity, sourceId); + } else if (entityName === 'branches') { await updateBranch(entity, sourceId); } @@ -48,17 +51,7 @@ async function updateEntity(sync, entity, sourceId) { } } -async function deserializeNoteContentBuffer(note) { - const noteContent = await note.getNoteContent(); - - if (noteContent.content !== null && (note.type === 'file' || note.type === 'image')) { - noteContent.content = Buffer.from(noteContent.content, 'base64'); - } -} - async function updateNote(entity, sourceId) { - await deserializeNoteContentBuffer(entity); - const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]); if (!origNote || origNote.dateModified <= entity.dateModified) { @@ -72,6 +65,22 @@ async function updateNote(entity, sourceId) { } } +async function updateNoteContent(entity, sourceId) { + const origNoteContent = await sql.getRow("SELECT * FROM note_contents WHERE noteId = ?", [entity.noteId]); + + if (!origNoteContent || origNoteContent.dateModified <= entity.dateModified) { + entity.content = entity.content === null ? null : Buffer.from(entity.content, 'base64'); + + await sql.transactional(async () => { + await sql.replace("note_contents", entity); + + await syncTableService.addNoteContentSync(entity.noteContentId, sourceId); + }); + + log.info("Update/sync note content " + entity.noteContentId); + } +} + async function updateBranch(entity, sourceId) { const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]); @@ -99,6 +108,8 @@ async function updateNoteRevision(entity, sourceId) { // we update note revision even if date modified to is the same because the only thing which might have changed // is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) { + entity.content = entity.content === null ? null : Buffer.from(entity.content, 'base64'); + await sql.replace('note_revisions', entity); await syncTableService.addNoteRevisionSync(entity.noteRevisionId, sourceId);