mirror of
https://github.com/zadam/trilium.git
synced 2025-06-05 01:18:44 +02:00
add memberId to entity_changes to avoid having to resend all changes second time
This commit is contained in:
parent
c448d34a38
commit
7159b13c9d
@ -44,7 +44,7 @@ find $DIR/node_modules -name demo -exec rm -rf {} \;
|
||||
|
||||
find $DIR/libraries -name "*.map" -type f -delete
|
||||
|
||||
rm -r $DIR/src/public/app
|
||||
rm -rf $DIR/src/public/app
|
||||
|
||||
sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs
|
||||
sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs
|
||||
|
@ -5,7 +5,8 @@ CREATE TABLE IF NOT EXISTS "entity_changes" (
|
||||
`hash` TEXT NOT NULL,
|
||||
`isErased` INT NOT NULL,
|
||||
`changeId` TEXT NOT NULL,
|
||||
`sourceId` TEXT NOT NULL,
|
||||
`componentId` TEXT NOT NULL,
|
||||
`memberId` TEXT NOT NULL,
|
||||
`isSynced` INTEGER NOT NULL,
|
||||
`utcDateChanged` TEXT NOT NULL
|
||||
);
|
||||
|
11486
package-lock.json
generated
11486
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -43,10 +43,10 @@
|
||||
"@electron/remote": "2.0.1",
|
||||
"express": "4.17.2",
|
||||
"express-partial-content": "^1.0.2",
|
||||
"express-rate-limit": "5.5.1",
|
||||
"express-rate-limit": "6.0.5",
|
||||
"express-session": "1.17.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"helmet": "4.6.0",
|
||||
"helmet": "5.0.1",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "5.0.0",
|
||||
@ -88,7 +88,7 @@
|
||||
"electron-packager": "15.4.0",
|
||||
"electron-rebuild": "3.2.5",
|
||||
"esm": "3.2.25",
|
||||
"jasmine": "3.10.0",
|
||||
"jasmine": "4.0.1",
|
||||
"jsdoc": "3.6.7",
|
||||
"lorem-ipsum": "2.0.4",
|
||||
"rcedit": "3.0.1",
|
||||
|
@ -41,7 +41,7 @@ function route(router, method, path, routeHandler) {
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
cls.init(() => {
|
||||
cls.set('sourceId', "etapi");
|
||||
cls.set('componentId', "etapi");
|
||||
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
|
||||
|
||||
const cb = () => routeHandler(req, res, next);
|
||||
@ -129,4 +129,4 @@ module.exports = {
|
||||
getAndCheckBranch,
|
||||
getAndCheckAttribute,
|
||||
getNotAllowedPatchPropertyError: (propertyName, allowedProperties) => new EtapiError(400, "PROPERTY_NOT_ALLOWED_FOR_PATCH", `Property '${propertyName}' is not allowed to be patched, allowed properties are ${allowedProperties}.`),
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ async function processEntityChanges(entityChanges) {
|
||||
} else if (ec.entityName === 'note_contents') {
|
||||
delete froca.noteComplementPromises[ec.entityId];
|
||||
|
||||
loadResults.addNoteContent(ec.entityId, ec.sourceId);
|
||||
loadResults.addNoteContent(ec.entityId, ec.componentId);
|
||||
} else if (ec.entityName === 'note_revisions') {
|
||||
loadResults.addNoteRevision(ec.entityId, ec.noteId, ec.sourceId);
|
||||
loadResults.addNoteRevision(ec.entityId, ec.noteId, ec.componentId);
|
||||
} else if (ec.entityName === 'note_revision_contents') {
|
||||
// this should change only when toggling isProtected, ignore
|
||||
} else if (ec.entityName === 'options') {
|
||||
@ -87,7 +87,7 @@ function processNoteChange(loadResults, ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadResults.addNote(ec.entityId, ec.sourceId);
|
||||
loadResults.addNote(ec.entityId, ec.componentId);
|
||||
|
||||
if (ec.isErased && ec.entityId in froca.notes) {
|
||||
utils.reloadFrontendApp(`${ec.entityName} ${ec.entityId} is erased, need to do complete reload.`);
|
||||
@ -125,7 +125,7 @@ function processBranchChange(loadResults, ec) {
|
||||
delete parentNote.childToBranch[branch.noteId];
|
||||
}
|
||||
|
||||
loadResults.addBranch(ec.entityId, ec.sourceId);
|
||||
loadResults.addBranch(ec.entityId, ec.componentId);
|
||||
|
||||
delete froca.branches[ec.entityId];
|
||||
}
|
||||
@ -133,7 +133,7 @@ function processBranchChange(loadResults, ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadResults.addBranch(ec.entityId, ec.sourceId);
|
||||
loadResults.addBranch(ec.entityId, ec.componentId);
|
||||
|
||||
const childNote = froca.notes[ec.entity.noteId];
|
||||
const parentNote = froca.notes[ec.entity.parentNoteId];
|
||||
@ -175,7 +175,7 @@ function processNoteReordering(loadResults, ec) {
|
||||
}
|
||||
}
|
||||
|
||||
loadResults.addNoteReordering(ec.entityId, ec.sourceId);
|
||||
loadResults.addNoteReordering(ec.entityId, ec.componentId);
|
||||
}
|
||||
|
||||
function processAttributeChange(loadResults, ec) {
|
||||
@ -199,7 +199,7 @@ function processAttributeChange(loadResults, ec) {
|
||||
targetNote.targetRelations = targetNote.targetRelations.filter(attributeId => attributeId !== attribute.attributeId);
|
||||
}
|
||||
|
||||
loadResults.addAttribute(ec.entityId, ec.sourceId);
|
||||
loadResults.addAttribute(ec.entityId, ec.componentId);
|
||||
|
||||
delete froca.attributes[ec.entityId];
|
||||
}
|
||||
@ -207,7 +207,7 @@ function processAttributeChange(loadResults, ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadResults.addAttribute(ec.entityId, ec.sourceId);
|
||||
loadResults.addAttribute(ec.entityId, ec.componentId);
|
||||
|
||||
const sourceNote = froca.notes[ec.entity.noteId];
|
||||
const targetNote = ec.entity.type === 'relation' && froca.notes[ec.entity.value];
|
||||
|
@ -9,8 +9,8 @@ export default class LoadResults {
|
||||
}
|
||||
}
|
||||
|
||||
this.noteIdToSourceId = {};
|
||||
this.sourceIdToNoteIds = {};
|
||||
this.noteIdToComponentId = {};
|
||||
this.componentIdToNoteIds = {};
|
||||
|
||||
this.branches = [];
|
||||
|
||||
@ -20,7 +20,7 @@ export default class LoadResults {
|
||||
|
||||
this.noteRevisions = [];
|
||||
|
||||
this.contentNoteIdToSourceId = [];
|
||||
this.contentNoteIdToComponentId = [];
|
||||
|
||||
this.options = [];
|
||||
}
|
||||
@ -29,22 +29,22 @@ export default class LoadResults {
|
||||
return this.entities[entityName]?.[entityId];
|
||||
}
|
||||
|
||||
addNote(noteId, sourceId) {
|
||||
this.noteIdToSourceId[noteId] = this.noteIdToSourceId[noteId] || [];
|
||||
addNote(noteId, componentId) {
|
||||
this.noteIdToComponentId[noteId] = this.noteIdToComponentId[noteId] || [];
|
||||
|
||||
if (!this.noteIdToSourceId[noteId].includes(sourceId)) {
|
||||
this.noteIdToSourceId[noteId].push(sourceId);
|
||||
if (!this.noteIdToComponentId[noteId].includes(componentId)) {
|
||||
this.noteIdToComponentId[noteId].push(componentId);
|
||||
}
|
||||
|
||||
this.sourceIdToNoteIds[sourceId] = this.sourceIdToNoteIds[sourceId] || [];
|
||||
this.componentIdToNoteIds[componentId] = this.componentIdToNoteIds[componentId] || [];
|
||||
|
||||
if (!this.sourceIdToNoteIds[sourceId]) {
|
||||
this.sourceIdToNoteIds[sourceId].push(noteId);
|
||||
if (!this.componentIdToNoteIds[componentId]) {
|
||||
this.componentIdToNoteIds[componentId].push(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
addBranch(branchId, sourceId) {
|
||||
this.branches.push({branchId, sourceId});
|
||||
addBranch(branchId, componentId) {
|
||||
this.branches.push({branchId, componentId});
|
||||
}
|
||||
|
||||
getBranches() {
|
||||
@ -53,7 +53,7 @@ export default class LoadResults {
|
||||
.filter(branch => !!branch);
|
||||
}
|
||||
|
||||
addNoteReordering(parentNoteId, sourceId) {
|
||||
addNoteReordering(parentNoteId, componentId) {
|
||||
this.noteReorderings.push(parentNoteId);
|
||||
}
|
||||
|
||||
@ -61,20 +61,20 @@ export default class LoadResults {
|
||||
return this.noteReorderings;
|
||||
}
|
||||
|
||||
addAttribute(attributeId, sourceId) {
|
||||
this.attributes.push({attributeId, sourceId});
|
||||
addAttribute(attributeId, componentId) {
|
||||
this.attributes.push({attributeId, componentId});
|
||||
}
|
||||
|
||||
/** @returns {Attribute[]} */
|
||||
getAttributes(sourceId = 'none') {
|
||||
getAttributes(componentId = 'none') {
|
||||
return this.attributes
|
||||
.filter(row => row.sourceId !== sourceId)
|
||||
.filter(row => row.componentId !== componentId)
|
||||
.map(row => this.getEntity("attributes", row.attributeId))
|
||||
.filter(attr => !!attr);
|
||||
}
|
||||
|
||||
addNoteRevision(noteRevisionId, noteId, sourceId) {
|
||||
this.noteRevisions.push({noteRevisionId, noteId, sourceId});
|
||||
addNoteRevision(noteRevisionId, noteId, componentId) {
|
||||
this.noteRevisions.push({noteRevisionId, noteId, componentId});
|
||||
}
|
||||
|
||||
hasNoteRevisionForNote(noteId) {
|
||||
@ -82,28 +82,28 @@ export default class LoadResults {
|
||||
}
|
||||
|
||||
getNoteIds() {
|
||||
return Object.keys(this.noteIdToSourceId);
|
||||
return Object.keys(this.noteIdToComponentId);
|
||||
}
|
||||
|
||||
isNoteReloaded(noteId, sourceId = null) {
|
||||
isNoteReloaded(noteId, componentId = null) {
|
||||
if (!noteId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceIds = this.noteIdToSourceId[noteId];
|
||||
return sourceIds && !!sourceIds.find(sId => sId !== sourceId);
|
||||
const componentIds = this.noteIdToComponentId[noteId];
|
||||
return componentIds && !!componentIds.find(sId => sId !== componentId);
|
||||
}
|
||||
|
||||
addNoteContent(noteId, sourceId) {
|
||||
this.contentNoteIdToSourceId.push({noteId, sourceId});
|
||||
addNoteContent(noteId, componentId) {
|
||||
this.contentNoteIdToComponentId.push({noteId, componentId});
|
||||
}
|
||||
|
||||
isNoteContentReloaded(noteId, sourceId) {
|
||||
isNoteContentReloaded(noteId, componentId) {
|
||||
if (!noteId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.contentNoteIdToSourceId.find(l => l.noteId === noteId && l.sourceId !== sourceId);
|
||||
return this.contentNoteIdToComponentId.find(l => l.noteId === noteId && l.componentId !== componentId);
|
||||
}
|
||||
|
||||
addOption(name) {
|
||||
@ -124,17 +124,17 @@ export default class LoadResults {
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return Object.keys(this.noteIdToSourceId).length === 0
|
||||
return Object.keys(this.noteIdToComponentId).length === 0
|
||||
&& this.branches.length === 0
|
||||
&& this.attributes.length === 0
|
||||
&& this.noteReorderings.length === 0
|
||||
&& this.noteRevisions.length === 0
|
||||
&& this.contentNoteIdToSourceId.length === 0
|
||||
&& this.contentNoteIdToComponentId.length === 0
|
||||
&& this.options.length === 0;
|
||||
}
|
||||
|
||||
isEmptyForTree() {
|
||||
return Object.keys(this.noteIdToSourceId).length === 0
|
||||
return Object.keys(this.noteIdToComponentId).length === 0
|
||||
&& this.branches.length === 0
|
||||
&& this.attributes.length === 0
|
||||
&& this.noteReorderings.length === 0;
|
||||
|
@ -9,7 +9,7 @@ async function getHeaders(headers) {
|
||||
// headers need to be lowercase because node.js automatically converts them to lower case
|
||||
// also avoiding using underscores instead of dashes since nginx filters them out by default
|
||||
const allHeaders = {
|
||||
'trilium-source-id': glob.sourceId,
|
||||
'trilium-component-id': glob.componentId,
|
||||
'trilium-local-now-datetime': utils.localNowDateTime(),
|
||||
'trilium-hoisted-note-id': activeNoteContext ? activeNoteContext.hoistedNoteId : null,
|
||||
'x-csrf-token': glob.csrfToken
|
||||
@ -29,20 +29,20 @@ async function getHeaders(headers) {
|
||||
return allHeaders;
|
||||
}
|
||||
|
||||
async function get(url, sourceId) {
|
||||
return await call('GET', url, null, {'trilium-source-id': sourceId});
|
||||
async function get(url, componentId) {
|
||||
return await call('GET', url, null, {'trilium-component-id': componentId});
|
||||
}
|
||||
|
||||
async function post(url, data, sourceId) {
|
||||
return await call('POST', url, data, {'trilium-source-id': sourceId});
|
||||
async function post(url, data, componentId) {
|
||||
return await call('POST', url, data, {'trilium-component-id': componentId});
|
||||
}
|
||||
|
||||
async function put(url, data, sourceId) {
|
||||
return await call('PUT', url, data, {'trilium-source-id': sourceId});
|
||||
async function put(url, data, componentId) {
|
||||
return await call('PUT', url, data, {'trilium-component-id': componentId});
|
||||
}
|
||||
|
||||
async function remove(url, sourceId) {
|
||||
return await call('DELETE', url, null, {'trilium-source-id': sourceId});
|
||||
async function remove(url, componentId) {
|
||||
return await call('DELETE', url, null, {'trilium-component-id': componentId});
|
||||
}
|
||||
|
||||
let i = 1;
|
||||
|
@ -21,6 +21,7 @@ function SetupModel() {
|
||||
|
||||
this.syncServerHost = ko.observable();
|
||||
this.syncProxy = ko.observable();
|
||||
this.password = ko.observable();
|
||||
|
||||
this.instanceType = utils.isElectron() ? "desktop" : "server";
|
||||
|
||||
@ -48,19 +49,13 @@ function SetupModel() {
|
||||
this.finish = async () => {
|
||||
const syncServerHost = this.syncServerHost();
|
||||
const syncProxy = this.syncProxy();
|
||||
const username = this.username();
|
||||
const password = this.password1();
|
||||
const password = this.password();
|
||||
|
||||
if (!syncServerHost) {
|
||||
showAlert("Trilium server address can't be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
showAlert("Username can't be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
showAlert("Password can't be empty");
|
||||
return;
|
||||
@ -70,7 +65,6 @@ function SetupModel() {
|
||||
const resp = await $.post('api/setup/sync-from-server', {
|
||||
syncServerHost: syncServerHost,
|
||||
syncProxy: syncProxy,
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
const options = require('../../services/options');
|
||||
const utils = require('../../services/utils');
|
||||
const dateUtils = require('../../services/date_utils');
|
||||
const sourceIdService = require('../../services/source_id');
|
||||
const memberId = require('../../services/member_id');
|
||||
const passwordEncryptionService = require('../../services/password_encryption');
|
||||
const protectedSessionService = require('../../services/protected_session');
|
||||
const appInfo = require('../../services/app_info');
|
||||
@ -47,7 +47,7 @@ function loginSync(req) {
|
||||
req.session.loggedIn = true;
|
||||
|
||||
return {
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
memberId: memberId,
|
||||
maxEntityChangeId: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1")
|
||||
};
|
||||
}
|
||||
|
@ -123,13 +123,36 @@ function forceNoteSync(req) {
|
||||
function getChanged(req) {
|
||||
const startTime = Date.now();
|
||||
|
||||
const lastEntityChangeId = parseInt(req.query.lastEntityChangeId);
|
||||
let lastEntityChangeId = parseInt(req.query.lastEntityChangeId);
|
||||
const clientMemberId = req.query.memberId;
|
||||
let filteredEntityChanges = [];
|
||||
|
||||
const entityChanges = sql.getRows("SELECT * FROM entity_changes WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastEntityChangeId]);
|
||||
while (filteredEntityChanges.length === 0) {
|
||||
const entityChanges = sql.getRows(`
|
||||
SELECT *
|
||||
FROM entity_changes
|
||||
WHERE isSynced = 1
|
||||
AND id > ?
|
||||
ORDER BY id
|
||||
LIMIT 1000`, [lastEntityChangeId]);
|
||||
|
||||
if (entityChanges.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
filteredEntityChanges = entityChanges.filter(ec => ec.memberId !== clientMemberId);
|
||||
}
|
||||
|
||||
const entityChangesRecords = syncService.getEntityChangesRecords(filteredEntityChanges);
|
||||
|
||||
if (entityChangesRecords.length > 0) {
|
||||
lastEntityChangeId = entityChangesRecords[entityChangesRecords.length - 1].entityChange.id;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
entityChanges: syncService.getEntityChangesRecords(entityChanges),
|
||||
maxEntityChangeId: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1')
|
||||
entityChanges: entityChangesRecords,
|
||||
maxEntityChangeId: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1'),
|
||||
lastEntityChangeId
|
||||
};
|
||||
|
||||
if (ret.entityChanges.length > 0) {
|
||||
@ -174,10 +197,10 @@ function update(req) {
|
||||
}
|
||||
}
|
||||
|
||||
const {entities} = body;
|
||||
const {entities, memberId} = body;
|
||||
|
||||
for (const {entityChange, entity} of entities) {
|
||||
syncUpdateService.updateEntity(entityChange, entity);
|
||||
syncUpdateService.updateEntity(entityChange, entity, memberId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const sourceIdService = require('../services/source_id');
|
||||
const sql = require('../services/sql');
|
||||
const attributeService = require('../services/attributes');
|
||||
const config = require('../services/config');
|
||||
@ -28,7 +27,6 @@ function index(req, res) {
|
||||
mainFontSize: parseInt(options.mainFontSize),
|
||||
treeFontSize: parseInt(options.treeFontSize),
|
||||
detailFontSize: parseInt(options.detailFontSize),
|
||||
sourceId: sourceIdService.generateSourceId(),
|
||||
maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"),
|
||||
maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"),
|
||||
instanceName: config.General ? config.General.instanceName : null,
|
||||
|
@ -144,7 +144,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
const result = cls.init(() => {
|
||||
cls.set('sourceId', req.headers['trilium-source-id']);
|
||||
cls.set('componentId', req.headers['trilium-component-id']);
|
||||
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
|
||||
cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root');
|
||||
|
||||
|
@ -4,7 +4,7 @@ const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 190;
|
||||
const APP_DB_VERSION = 191;
|
||||
const SYNC_VERSION = 25;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
|
@ -28,8 +28,8 @@ function getHoistedNoteId() {
|
||||
return namespace.get('hoistedNoteId') || 'root';
|
||||
}
|
||||
|
||||
function getSourceId() {
|
||||
return namespace.get('sourceId');
|
||||
function getComponentId() {
|
||||
return namespace.get('componentId');
|
||||
}
|
||||
|
||||
function getLocalNowDateTime() {
|
||||
@ -80,7 +80,7 @@ module.exports = {
|
||||
set,
|
||||
namespace,
|
||||
getHoistedNoteId,
|
||||
getSourceId,
|
||||
getComponentId,
|
||||
getLocalNowDateTime,
|
||||
disableEntityEvents,
|
||||
isEntityEventsDisabled,
|
||||
|
@ -8,9 +8,9 @@ function getEntityHashes() {
|
||||
const startTime = new Date();
|
||||
|
||||
const hashRows = sql.getRawRows(`
|
||||
SELECT entityName,
|
||||
entityId,
|
||||
hash
|
||||
SELECT entityName,
|
||||
entityId,
|
||||
hash
|
||||
FROM entity_changes
|
||||
WHERE isSynced = 1
|
||||
AND entityName != 'note_reordering'`);
|
||||
|
@ -1,13 +1,19 @@
|
||||
const sql = require('./sql');
|
||||
const sourceIdService = require('./source_id');
|
||||
const dateUtils = require('./date_utils');
|
||||
const log = require('./log');
|
||||
const cls = require('./cls');
|
||||
const utils = require('./utils');
|
||||
const memberId = require('./member_id');
|
||||
const becca = require("../becca/becca");
|
||||
|
||||
let maxEntityChangeId = 0;
|
||||
|
||||
function addEntityChangeWithMemberId(origEntityChange, memberId) {
|
||||
const ec = {...origEntityChange, memberId};
|
||||
|
||||
return addEntityChange(ec);
|
||||
}
|
||||
|
||||
function addEntityChange(origEntityChange) {
|
||||
const ec = {...origEntityChange};
|
||||
|
||||
@ -17,7 +23,8 @@ function addEntityChange(origEntityChange) {
|
||||
ec.changeId = utils.randomString(12);
|
||||
}
|
||||
|
||||
ec.sourceId = ec.sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId();
|
||||
ec.componentId = ec.componentId || cls.getComponentId() || "";
|
||||
ec.memberId = ec.memberId || memberId;
|
||||
ec.isSynced = ec.isSynced ? 1 : 0;
|
||||
ec.isErased = ec.isErased ? 1 : 0;
|
||||
ec.id = sql.replace("entity_changes", ec);
|
||||
@ -27,7 +34,7 @@ function addEntityChange(origEntityChange) {
|
||||
cls.addEntityChange(ec);
|
||||
}
|
||||
|
||||
function addNoteReorderingEntityChange(parentNoteId, sourceId) {
|
||||
function addNoteReorderingEntityChange(parentNoteId, componentId) {
|
||||
addEntityChange({
|
||||
entityName: "note_reordering",
|
||||
entityId: parentNoteId,
|
||||
@ -35,7 +42,8 @@ function addNoteReorderingEntityChange(parentNoteId, sourceId) {
|
||||
isErased: false,
|
||||
utcDateChanged: dateUtils.utcNowDateTime(),
|
||||
isSynced: true,
|
||||
sourceId
|
||||
componentId,
|
||||
memberId: memberId
|
||||
});
|
||||
|
||||
const eventService = require('./events');
|
||||
@ -138,6 +146,7 @@ module.exports = {
|
||||
addNoteReorderingEntityChange,
|
||||
moveEntityChangeToTop,
|
||||
addEntityChange,
|
||||
addEntityChangeWithMemberId,
|
||||
fillAllEntityChanges,
|
||||
addEntityChangesForSector,
|
||||
getMaxEntityChangeId: () => maxEntityChangeId
|
||||
|
5
src/services/member_id.js
Normal file
5
src/services/member_id.js
Normal file
@ -0,0 +1,5 @@
|
||||
const utils = require('./utils');
|
||||
|
||||
const memberId = utils.randomString(12);
|
||||
|
||||
module.exports = memberId;
|
@ -30,9 +30,9 @@ function executeBundle(bundle, apiParams = {}) {
|
||||
apiParams.startNote = bundle.note;
|
||||
}
|
||||
|
||||
const originalSourceId = cls.get('sourceId');
|
||||
const originalComponentId = cls.get('componentId');
|
||||
|
||||
cls.set('sourceId', 'script');
|
||||
cls.set('componentId', 'script');
|
||||
|
||||
// last \r\n is necessary if script contains line comment on its last line
|
||||
const script = "function() {\r\n" + bundle.script + "\r\n}";
|
||||
@ -47,7 +47,7 @@ function executeBundle(bundle, apiParams = {}) {
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
cls.set('sourceId', originalSourceId);
|
||||
cls.set('componentId', originalComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
const utils = require('./utils');
|
||||
|
||||
const localSourceIds = {};
|
||||
|
||||
function generateSourceId() {
|
||||
const sourceId = utils.randomString(12);
|
||||
|
||||
localSourceIds[sourceId] = true;
|
||||
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
function isLocalSourceId(srcId) {
|
||||
return !!localSourceIds[srcId];
|
||||
}
|
||||
|
||||
const currentSourceId = generateSourceId();
|
||||
|
||||
function getCurrentSourceId() {
|
||||
return currentSourceId;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateSourceId,
|
||||
getCurrentSourceId,
|
||||
isLocalSourceId
|
||||
};
|
@ -4,7 +4,7 @@ const log = require('./log');
|
||||
const sql = require('./sql');
|
||||
const optionService = require('./options');
|
||||
const utils = require('./utils');
|
||||
const sourceIdService = require('./source_id');
|
||||
const memberId = require('./member_id');
|
||||
const dateUtils = require('./date_utils');
|
||||
const syncUpdateService = require('./sync_update');
|
||||
const contentHashService = require('./content_hash');
|
||||
@ -107,11 +107,11 @@ async function doLogin() {
|
||||
hash: hash
|
||||
});
|
||||
|
||||
if (sourceIdService.isLocalSourceId(resp.sourceId)) {
|
||||
throw new Error(`Sync server has source ID ${resp.sourceId} which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`);
|
||||
if (resp.memberId === memberId) {
|
||||
throw new Error(`Sync server has member ID ${resp.memberId} which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`);
|
||||
}
|
||||
|
||||
syncContext.sourceId = resp.sourceId;
|
||||
syncContext.memberId = resp.memberId;
|
||||
|
||||
const lastSyncedPull = getLastSyncedPull();
|
||||
|
||||
@ -131,7 +131,7 @@ async function pullChanges(syncContext) {
|
||||
|
||||
while (true) {
|
||||
const lastSyncedPull = getLastSyncedPull();
|
||||
const changesUri = '/api/sync/changed?lastEntityChangeId=' + lastSyncedPull;
|
||||
const changesUri = `/api/sync/changed?memberId=${memberId}&lastEntityChangeId=${lastSyncedPull}`;
|
||||
|
||||
const startDate = Date.now();
|
||||
|
||||
@ -141,36 +141,38 @@ async function pullChanges(syncContext) {
|
||||
|
||||
outstandingPullCount = Math.max(0, resp.maxEntityChangeId - lastSyncedPull);
|
||||
|
||||
const {entityChanges} = resp;
|
||||
|
||||
if (entityChanges.length === 0) {
|
||||
break;
|
||||
}
|
||||
const {entityChanges, lastEntityChangeId} = resp;
|
||||
|
||||
sql.transactional(() => {
|
||||
for (const {entityChange, entity} of entityChanges) {
|
||||
const changeAppliedAlready = !entityChange.changeId
|
||||
|| !!sql.getValue("SELECT id FROM entity_changes WHERE changeId = ?", [entityChange.changeId]);
|
||||
|
||||
if (!changeAppliedAlready && !sourceIdService.isLocalSourceId(entityChange.sourceId)) {
|
||||
if (!changeAppliedAlready) {
|
||||
if (!atLeastOnePullApplied) { // send only for first
|
||||
ws.syncPullInProgress();
|
||||
|
||||
atLeastOnePullApplied = true;
|
||||
}
|
||||
|
||||
syncUpdateService.updateEntity(entityChange, entity);
|
||||
syncUpdateService.updateEntity(entityChange, entity, syncContext.memberId);
|
||||
}
|
||||
|
||||
outstandingPullCount = Math.max(0, resp.maxEntityChangeId - entityChange.id);
|
||||
}
|
||||
|
||||
setLastSyncedPull(entityChanges[entityChanges.length - 1].entityChange.id);
|
||||
if (lastSyncedPull !== lastEntityChangeId) {
|
||||
setLastSyncedPull(lastEntityChangeId);
|
||||
}
|
||||
});
|
||||
|
||||
const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
|
||||
if (entityChanges.length === 0) {
|
||||
break;
|
||||
} else {
|
||||
const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
|
||||
|
||||
log.info(`Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
|
||||
log.info(`Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Finished pull");
|
||||
@ -189,10 +191,9 @@ async function pushChanges(syncContext) {
|
||||
}
|
||||
|
||||
const filteredEntityChanges = entityChanges.filter(entityChange => {
|
||||
if (entityChange.sourceId === syncContext.sourceId) {
|
||||
if (entityChange.memberId === syncContext.memberId) {
|
||||
// this may set lastSyncedPush beyond what's actually sent (because of size limit)
|
||||
// so this is applied to the database only if there's no actual update
|
||||
// TODO: it would be better to simplify this somehow
|
||||
lastSyncedPush = entityChange.id;
|
||||
|
||||
return false;
|
||||
@ -214,8 +215,8 @@ async function pushChanges(syncContext) {
|
||||
const startDate = new Date();
|
||||
|
||||
await syncRequest(syncContext, 'PUT', '/api/sync/update', {
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
entities: entityChangesRecords
|
||||
entities: entityChangesRecords,
|
||||
memberId
|
||||
});
|
||||
|
||||
ws.syncPushInProgress();
|
||||
|
@ -4,12 +4,12 @@ const entityChangesService = require('./entity_changes');
|
||||
const eventService = require('./events');
|
||||
const entityConstructor = require("../becca/entity_constructor");
|
||||
|
||||
function updateEntity(entityChange, entityRow) {
|
||||
function updateEntity(entityChange, entityRow, memberId) {
|
||||
// can be undefined for options with isSynced=false
|
||||
if (!entityRow) {
|
||||
if (entityChange.isSynced) {
|
||||
if (entityChange.isErased) {
|
||||
eraseEntity(entityChange);
|
||||
eraseEntity(entityChange, memberId);
|
||||
}
|
||||
else {
|
||||
log.info(`Encountered synced non-erased entity change without entity: ${JSON.stringify(entityChange)}`);
|
||||
@ -23,8 +23,8 @@ function updateEntity(entityChange, entityRow) {
|
||||
}
|
||||
|
||||
const updated = entityChange.entityName === 'note_reordering'
|
||||
? updateNoteReordering(entityChange, entityRow)
|
||||
: updateNormalEntity(entityChange, entityRow);
|
||||
? updateNoteReordering(entityChange, entityRow, memberId)
|
||||
: updateNormalEntity(entityChange, entityRow, memberId);
|
||||
|
||||
if (updated) {
|
||||
if (entityRow.isDeleted) {
|
||||
@ -42,7 +42,7 @@ function updateEntity(entityChange, entityRow) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateNormalEntity(remoteEntityChange, entity) {
|
||||
function updateNormalEntity(remoteEntityChange, entity, memberId) {
|
||||
const localEntityChange = sql.getRow(`
|
||||
SELECT utcDateChanged, hash, isErased
|
||||
FROM entity_changes
|
||||
@ -54,7 +54,7 @@ function updateNormalEntity(remoteEntityChange, entity) {
|
||||
|
||||
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
|
||||
|
||||
entityChangesService.addEntityChange(remoteEntityChange);
|
||||
entityChangesService.addEntityChangeWithMemberId(remoteEntityChange, memberId);
|
||||
});
|
||||
|
||||
return true;
|
||||
@ -71,7 +71,7 @@ function updateNormalEntity(remoteEntityChange, entity) {
|
||||
sql.transactional(() => {
|
||||
sql.replace(remoteEntityChange.entityName, entity);
|
||||
|
||||
entityChangesService.addEntityChange(remoteEntityChange);
|
||||
entityChangesService.addEntityChangeWithMemberId(remoteEntityChange, memberId);
|
||||
});
|
||||
|
||||
return true;
|
||||
@ -80,13 +80,13 @@ function updateNormalEntity(remoteEntityChange, entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateNoteReordering(entityChange, entity) {
|
||||
function updateNoteReordering(entityChange, entity, memberId) {
|
||||
sql.transactional(() => {
|
||||
for (const key in entity) {
|
||||
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
|
||||
}
|
||||
|
||||
entityChangesService.addEntityChange(entityChange);
|
||||
entityChangesService.addEntityChangeWithMemberId(entityChange, memberId);
|
||||
});
|
||||
|
||||
return true;
|
||||
@ -105,7 +105,7 @@ function handleContent(content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function eraseEntity(entityChange) {
|
||||
function eraseEntity(entityChange, memberId) {
|
||||
const {entityName, entityId} = entityChange;
|
||||
|
||||
if (!["notes", "note_contents", "branches", "attributes", "note_revisions", "note_revision_contents"].includes(entityName)) {
|
||||
@ -119,7 +119,7 @@ function eraseEntity(entityChange) {
|
||||
|
||||
eventService.emit(eventService.ENTITY_DELETE_SYNCED, { entityName, entityId });
|
||||
|
||||
entityChangesService.addEntityChange(entityChange, true);
|
||||
entityChangesService.addEntityChangeWithMemberId(entityChange, memberId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -48,7 +48,6 @@
|
||||
window.device = "desktop";
|
||||
window.glob = {
|
||||
activeDialog: null,
|
||||
sourceId: '<%= sourceId %>',
|
||||
maxEntityChangeIdAtLoad: <%= maxEntityChangeIdAtLoad %>,
|
||||
maxEntityChangeSyncIdAtLoad: <%= maxEntityChangeSyncIdAtLoad %>,
|
||||
instanceName: '<%= instanceName %>',
|
||||
|
@ -111,7 +111,6 @@
|
||||
window.device = "mobile";
|
||||
window.glob = {
|
||||
activeDialog: null,
|
||||
sourceId: '<%= sourceId %>',
|
||||
maxEntityChangeIdAtLoad: <%= maxEntityChangeIdAtLoad %>,
|
||||
maxEntityChangeSyncIdAtLoad: <%= maxEntityChangeSyncIdAtLoad %>,
|
||||
instanceName: '<%= instanceName %>',
|
||||
|
@ -119,8 +119,8 @@
|
||||
<p><strong>Note:</strong> If you leave proxy setting blank, system proxy will be used (applies to desktop/electron build only)</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password1">Password</label>
|
||||
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" class="form-control" data-bind="value: password" placeholder="Password">
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
@ -146,7 +146,7 @@
|
||||
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
|
||||
|
||||
window.glob = {
|
||||
sourceId: ''
|
||||
componentId: ''
|
||||
};
|
||||
|
||||
window.syncInProgress = <%= syncInProgress ? 'true' : 'false' %>;
|
||||
|
Loading…
x
Reference in New Issue
Block a user