From 6a6bd4541a293b6f07eb6807e0d8f335747354d6 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 17 Oct 2021 14:44:59 +0200 Subject: [PATCH] sharing WIP --- src/becca/becca_loader.js | 6 +- src/routes/routes.js | 3 + src/share/entities/branch.js | 90 --------- src/share/routes.js | 21 ++ src/share/shaca/entities/abstract_entity.js | 13 ++ src/share/{ => shaca}/entities/attribute.js | 52 +---- src/share/shaca/entities/branch.js | 65 +++++++ src/share/{ => shaca}/entities/note.js | 33 +--- src/share/shaca/shaca_loader.js | 205 +++----------------- src/share/share_root.js | 3 + 10 files changed, 158 insertions(+), 333 deletions(-) delete mode 100644 src/share/entities/branch.js create mode 100644 src/share/routes.js create mode 100644 src/share/shaca/entities/abstract_entity.js rename src/share/{ => shaca}/entities/attribute.js (52%) create mode 100644 src/share/shaca/entities/branch.js rename src/share/{ => shaca}/entities/note.js (96%) create mode 100644 src/share/share_root.js diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index 7018110a0..7d04b3649 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -29,15 +29,15 @@ function load() { // using raw query and passing arrays to avoid allocating new objects // this is worth it for becca load since it happens every run and blocks the app until finished - for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`, [])) { + for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`)) { new Note().update(row).init(); } - for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`, [])) { + for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`)) { new Branch().update(row).init(); } - for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`, [])) { + for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`)) { new Attribute().update(row).init(); } diff --git a/src/routes/routes.js b/src/routes/routes.js index 1d71ca8f6..6fa32e513 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -39,6 +39,7 @@ const keysRoute = require('./api/keys'); const backendLogRoute = require('./api/backend_log'); const statsRoute = require('./api/stats'); const fontsRoute = require('./api/fonts'); +const shareRoutes = require('../share/routes'); const log = require('../services/log'); const express = require('express'); @@ -365,6 +366,8 @@ function register(app) { route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss); + shareRoutes.register(router); + app.use('', router); } diff --git a/src/share/entities/branch.js b/src/share/entities/branch.js deleted file mode 100644 index 24af4d858..000000000 --- a/src/share/entities/branch.js +++ /dev/null @@ -1,90 +0,0 @@ -"use strict"; - -const Note = require('./note.js'); -const sql = require("../sql.js"); - -class Branch { - constructor(row) { - this.updateFromRow(row); - this.init(); - } - - updateFromRow(row) { - this.update([ - row.branchId, - row.noteId, - row.parentNoteId, - row.prefix, - row.notePosition, - row.isExpanded, - row.utcDateModified - ]); - } - - update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded]) { - /** @param {string} */ - this.branchId = branchId; - /** @param {string} */ - this.noteId = noteId; - /** @param {string} */ - this.parentNoteId = parentNoteId; - /** @param {string} */ - this.prefix = prefix; - /** @param {int} */ - this.notePosition = notePosition; - /** @param {boolean} */ - this.isExpanded = !!isExpanded; - - return this; - } - - init() { - if (this.branchId === 'root') { - return; - } - - const childNote = this.childNote; - const parentNote = this.parentNote; - - if (!childNote.parents.includes(parentNote)) { - childNote.parents.push(parentNote); - } - - if (!childNote.parentBranches.includes(this)) { - childNote.parentBranches.push(this); - } - - if (!parentNote.children.includes(childNote)) { - parentNote.children.push(childNote); - } - - this.becca.branches[this.branchId] = this; - this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; - } - - /** @return {Note} */ - get childNote() { - if (!(this.noteId in this.becca.notes)) { - // entities can come out of order in sync, create skeleton which will be filled later - this.becca.addNote(this.noteId, new Note({noteId: this.noteId})); - } - - return this.becca.notes[this.noteId]; - } - - getNote() { - return this.childNote; - } - - /** @return {Note} */ - get parentNote() { - if (!(this.parentNoteId in this.becca.notes)) { - // entities can come out of order in sync, create skeleton which will be filled later - this.becca.addNote(this.parentNoteId, new Note({noteId: this.parentNoteId})); - } - - return this.becca.notes[this.parentNoteId]; - } -} - -module.exports = Branch; diff --git a/src/share/routes.js b/src/share/routes.js new file mode 100644 index 000000000..525821b4e --- /dev/null +++ b/src/share/routes.js @@ -0,0 +1,21 @@ +const shaca = require("./shaca/shaca"); +const shacaLoader = require("./shaca/shaca_loader"); + +function register(router) { + router.get('/share/:noteId', (req, res, next) => { + const {noteId} = req.params; + + shacaLoader.ensureLoad(); + + if (noteId in shaca.notes) { + res.send(shaca.notes[noteId].title); + } + else { + res.send("FFF"); + } + }); +} + +module.exports = { + register +} diff --git a/src/share/shaca/entities/abstract_entity.js b/src/share/shaca/entities/abstract_entity.js new file mode 100644 index 000000000..2b27d9997 --- /dev/null +++ b/src/share/shaca/entities/abstract_entity.js @@ -0,0 +1,13 @@ +let shaca; + +class AbstractEntity { + get shaca() { + if (!shaca) { + shaca = require("../shaca"); + } + + return shaca; + } +} + +module.exports = AbstractEntity; diff --git a/src/share/entities/attribute.js b/src/share/shaca/entities/attribute.js similarity index 52% rename from src/share/entities/attribute.js rename to src/share/shaca/entities/attribute.js index eb7ce2ebd..986a3dad5 100644 --- a/src/share/entities/attribute.js +++ b/src/share/shaca/entities/attribute.js @@ -1,27 +1,11 @@ "use strict"; -const Note = require('./note.js'); -const sql = require("../sql.js"); +const AbstractEntity = require('./abstract_entity'); -class Attribute { - constructor(row) { - this.updateFromRow(row); - this.init(); - } +class Attribute extends AbstractEntity { + constructor([attributeId, noteId, type, name, value, isInheritable, position]) { + super(); - updateFromRow(row) { - this.update([ - row.attributeId, - row.noteId, - row.type, - row.name, - row.value, - row.isInheritable, - row.position - ]); - } - - update([attributeId, noteId, type, name, value, isInheritable, position]) { /** @param {string} */ this.attributeId = attributeId; /** @param {string} */ @@ -37,24 +21,8 @@ class Attribute { /** @param {boolean} */ this.isInheritable = !!isInheritable; - return this; - } - - init() { - if (this.attributeId) { - this.becca.attributes[this.attributeId] = this; - } - - if (!(this.noteId in this.becca.notes)) { - // entities can come out of order in sync, create skeleton which will be filled later - this.becca.addNote(this.noteId, new Note({noteId: this.noteId})); - } - - this.becca.notes[this.noteId].ownedAttributes.push(this); - - const key = `${this.type}-${this.name.toLowerCase()}`; - this.becca.attributeIndex[key] = this.becca.attributeIndex[key] || []; - this.becca.attributeIndex[key].push(this); + this.shaca.attributes[this.attributeId] = this; + this.shaca.notes[this.noteId].ownedAttributes.push(this); const targetNote = this.targetNote; @@ -77,12 +45,12 @@ class Attribute { } get note() { - return this.becca.notes[this.noteId]; + return this.shaca.notes[this.noteId]; } get targetNote() { if (this.type === 'relation') { - return this.becca.notes[this.value]; + return this.shaca.notes[this.value]; } } @@ -90,7 +58,7 @@ class Attribute { * @returns {Note|null} */ getNote() { - return this.becca.getNote(this.noteId); + return this.shaca.getNote(this.noteId); } /** @@ -105,7 +73,7 @@ class Attribute { return null; } - return this.becca.getNote(this.value); + return this.shaca.getNote(this.value); } } diff --git a/src/share/shaca/entities/branch.js b/src/share/shaca/entities/branch.js new file mode 100644 index 000000000..bf4ed2070 --- /dev/null +++ b/src/share/shaca/entities/branch.js @@ -0,0 +1,65 @@ +"use strict"; + +const AbstractEntity = require('./abstract_entity'); +const shareRoot = require("../../share_root"); + +class Branch extends AbstractEntity { + constructor([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded]) { + super(); + + /** @param {string} */ + this.branchId = branchId; + /** @param {string} */ + this.noteId = noteId; + /** @param {string} */ + this.parentNoteId = parentNoteId; + /** @param {string} */ + this.prefix = prefix; + /** @param {int} */ + this.notePosition = notePosition; + /** @param {boolean} */ + this.isExpanded = !!isExpanded; + + if (this.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { + return; + } + + const childNote = this.childNote; + const parentNote = this.parentNote; + + if (!childNote.parents.includes(parentNote)) { + childNote.parents.push(parentNote); + } + + if (!childNote.parentBranches.includes(this)) { + childNote.parentBranches.push(this); + } + + if (!parentNote) { + console.log(this); + } + + if (!parentNote.children.includes(childNote)) { + parentNote.children.push(childNote); + } + + this.shaca.branches[this.branchId] = this; + this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; + } + + /** @return {Note} */ + get childNote() { + return this.shaca.notes[this.noteId]; + } + + getNote() { + return this.childNote; + } + + /** @return {Note} */ + get parentNote() { + return this.shaca.notes[this.parentNoteId]; + } +} + +module.exports = Branch; diff --git a/src/share/entities/note.js b/src/share/shaca/entities/note.js similarity index 96% rename from src/share/entities/note.js rename to src/share/shaca/entities/note.js index 070f9481b..91268dcee 100644 --- a/src/share/entities/note.js +++ b/src/share/shaca/entities/note.js @@ -1,27 +1,16 @@ "use strict"; -const sql = require('../sql'); -const utils = require('../../services/utils'); +const sql = require('../../sql'); +const utils = require('../../../services/utils'); +const AbstractEntity = require('./abstract_entity'); const LABEL = 'label'; const RELATION = 'relation'; -class Note { - constructor(row) { - this.updateFromRow(row); - this.init(); - } +class Note extends AbstractEntity { + constructor([noteId, title, type, mime]) { + super(); - updateFromRow(row) { - this.update([ - row.noteId, - row.title, - row.type, - row.mime - ]); - } - - update([noteId, title, type, mime]) { /** @param {string} */ this.noteId = noteId; /** @param {string} */ @@ -31,10 +20,6 @@ class Note { /** @param {string} */ this.mime = mime; - return this; - } - - init() { /** @param {Branch[]} */ this.parentBranches = []; /** @param {Note[]} */ @@ -52,7 +37,7 @@ class Note { /** @param {Attribute[]} */ this.targetRelations = []; - this.becca.addNote(this.noteId, this); + this.shaca.notes[this.noteId] = this; /** @param {Note[]|null} */ this.ancestorCache = null; @@ -79,7 +64,7 @@ class Note { } getChildBranches() { - return this.children.map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); + return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); } getContent(silentNotFoundError = false) { @@ -182,7 +167,7 @@ class Note { for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates if (ownedAttr.type === 'relation' && ownedAttr.name === 'template') { - const templateNote = this.becca.notes[ownedAttr.value]; + const templateNote = this.shaca.notes[ownedAttr.value]; if (templateNote) { templateAttributes.push(...templateNote.__getAttributes(newPath)); diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index c5904ef3c..8cbb91abb 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -1,37 +1,49 @@ "use strict"; -const sql = require('../services/sql'); -const eventService = require('../services/events'); +const sql = require('../sql'); const shaca = require('./shaca.js'); -const sqlInit = require('../services/sql_init'); -const log = require('../services/log'); +const log = require('../../services/log'); const Note = require('./entities/note'); const Branch = require('./entities/branch'); const Attribute = require('./entities/attribute'); -const Option = require('./entities/option'); -const entityConstructor = require("../becca/entity_constructor"); +const shareRoot = require('../share_root'); function load() { const start = Date.now(); shaca.reset(); // using raw query and passing arrays to avoid allocating new objects - // this is worth it for becca load since it happens every run and blocks the app until finished - for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`, [])) { - new Note().update(row).init(); + const noteIds = sql.getColumn(` + WITH RECURSIVE + tree(noteId) AS ( + SELECT ? + UNION + SELECT branches.noteId FROM branches + JOIN tree ON branches.parentNoteId = tree.noteId + WHERE branches.isDeleted = 0 + ) + SELECT noteId FROM tree`, [shareRoot.SHARE_ROOT_NOTE_ID]); + + if (noteIds.length === 0) { + shaca.loaded = true; + + return; } - for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`, [])) { - new Branch().update(row).init(); + const noteIdStr = noteIds.map(noteId => `'${noteId}'`).join(","); + + for (const row of sql.getRawRows(`SELECT noteId, title, type, mime FROM notes WHERE isDeleted = 0 AND noteId IN (${noteIdStr})`)) { + new Note(row); } - for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`, [])) { - new Attribute().update(row).init(); + for (const row of sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0 AND noteId IN (${noteIdStr})`)) { + new Branch(row); } - for (const row of sql.getRows(`SELECT name, value, isSynced, utcDateModified FROM options`)) { - new Option(row); + // TODO: add filter for allowed attributes + for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0 AND noteId IN (${noteIdStr})`, [])) { + new Attribute(row); } shaca.loaded = true; @@ -39,169 +51,14 @@ function load() { log.info(`Shaca load took ${Date.now() - start}ms`); } -eventService.subscribe([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => { - if (!becca.loaded) { - return; - } - - if (["notes", "branches", "attributes"].includes(entityName)) { - const EntityClass = entityConstructor.getEntityFromEntityName(entityName); - const primaryKeyName = EntityClass.primaryKeyName; - - let beccaEntity = becca.getEntity(entityName, entityRow[primaryKeyName]); - - if (beccaEntity) { - beccaEntity.updateFromRow(entityRow); - } else { - beccaEntity = new EntityClass(); - beccaEntity.updateFromRow(entityRow); - beccaEntity.init(); - } - } - - postProcessEntityUpdate(entityName, entityRow); -}); - -eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => { - if (!becca.loaded) { - return; - } - - postProcessEntityUpdate(entityName, entity); -}); - -eventService.subscribe([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => { - if (!becca.loaded) { - return; - } - - if (entityName === 'notes') { - noteDeleted(entityId); - } else if (entityName === 'branches') { - branchDeleted(entityId); - } else if (entityName === 'attributes') { - attributeDeleted(entityId); - } -}); - -function noteDeleted(noteId) { - delete becca.notes[noteId]; - - becca.dirtyNoteSetCache(); -} - -function branchDeleted(branchId) { - const branch = becca.branches[branchId]; - - if (!branch) { - return; - } - - const childNote = becca.notes[branch.noteId]; - - if (childNote) { - childNote.parents = childNote.parents.filter(parent => parent.noteId !== branch.parentNoteId); - childNote.parentBranches = childNote.parentBranches - .filter(parentBranch => parentBranch.branchId !== branch.branchId); - - if (childNote.parents.length > 0) { - childNote.invalidateSubTree(); - } - } - - const parentNote = becca.notes[branch.parentNoteId]; - - if (parentNote) { - parentNote.children = parentNote.children.filter(child => child.noteId !== branch.noteId); - } - - delete becca.childParentToBranch[`${branch.noteId}-${branch.parentNoteId}`]; - delete becca.branches[branch.branchId]; -} - -function branchUpdated(branch) { - const childNote = becca.notes[branch.noteId]; - - if (childNote) { - childNote.flatTextCache = null; - childNote.resortParents(); +function ensureLoad() { + if (!shaca.loaded) { + load(); } } -function attributeDeleted(attributeId) { - const attribute = becca.attributes[attributeId]; - - if (!attribute) { - return; - } - - const note = becca.notes[attribute.noteId]; - - if (note) { - // first invalidate and only then remove the attribute (otherwise invalidation wouldn't be complete) - if (attribute.isAffectingSubtree || note.isTemplate()) { - note.invalidateSubTree(); - } else { - note.invalidateThisCache(); - } - - note.ownedAttributes = note.ownedAttributes.filter(attr => attr.attributeId !== attribute.attributeId); - - const targetNote = attribute.targetNote; - - if (targetNote) { - targetNote.targetRelations = targetNote.targetRelations.filter(rel => rel.attributeId !== attribute.attributeId); - } - } - - delete becca.attributes[attribute.attributeId]; - - const key = `${attribute.type}-${attribute.name.toLowerCase()}`; - - if (key in becca.attributeIndex) { - becca.attributeIndex[key] = becca.attributeIndex[key].filter(attr => attr.attributeId !== attribute.attributeId); - } -} - -function attributeUpdated(attribute) { - const note = becca.notes[attribute.noteId]; - - if (note) { - if (attribute.isAffectingSubtree || note.isTemplate()) { - note.invalidateSubTree(); - } else { - note.invalidateThisCache(); - } - } -} - -function noteReorderingUpdated(branchIdList) { - const parentNoteIds = new Set(); - - for (const branchId in branchIdList) { - const branch = becca.branches[branchId]; - - if (branch) { - branch.notePosition = branchIdList[branchId]; - - parentNoteIds.add(branch.parentNoteId); - } - } -} - -eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { - try { - becca.decryptProtectedNotes(); - } - catch (e) { - log.error(`Could not decrypt protected notes: ${e.message} ${e.stack}`); - } -}); - -eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, load); module.exports = { load, - reload, - beccaLoaded + ensureLoad }; diff --git a/src/share/share_root.js b/src/share/share_root.js new file mode 100644 index 000000000..9b4684681 --- /dev/null +++ b/src/share/share_root.js @@ -0,0 +1,3 @@ +module.exports = { + SHARE_ROOT_NOTE_ID: 'HX6aiJr1yjK4' +}