sharing WIP

This commit is contained in:
zadam 2021-10-17 14:44:59 +02:00
parent a14aa461ca
commit 6a6bd4541a
10 changed files with 158 additions and 333 deletions

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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;

21
src/share/routes.js Normal file
View file

@ -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
}

View file

@ -0,0 +1,13 @@
let shaca;
class AbstractEntity {
get shaca() {
if (!shaca) {
shaca = require("../shaca");
}
return shaca;
}
}
module.exports = AbstractEntity;

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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));

View file

@ -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
};

3
src/share/share_root.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
SHARE_ROOT_NOTE_ID: 'HX6aiJr1yjK4'
}