mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	add memberId to entity_changes to avoid having to resend all changes second time
This commit is contained in:
		| @@ -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' %>; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user