mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	#98, sync setup now doesn't copy the whole DB file, but sets up minimal database and starts off sync
This commit is contained in:
		
							
								
								
									
										2
									
								
								db/migrations/0102__fix_sync_entityIds.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								db/migrations/0102__fix_sync_entityIds.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | DELETE FROM sync WHERE entityName = 'note_tree'; | ||||||
|  | DELETE FROM sync WHERE entityName = 'attributes'; | ||||||
| @@ -61,7 +61,6 @@ | |||||||
|     "simple-node-logger": "^0.93.37", |     "simple-node-logger": "^0.93.37", | ||||||
|     "sqlite": "^2.9.2", |     "sqlite": "^2.9.2", | ||||||
|     "tar-stream": "^1.6.1", |     "tar-stream": "^1.6.1", | ||||||
|     "tmp-promise": "^1.0.5", |  | ||||||
|     "unescape": "^1.0.1", |     "unescape": "^1.0.1", | ||||||
|     "ws": "^5.2.1", |     "ws": "^5.2.1", | ||||||
|     "xml2js": "^0.4.19" |     "xml2js": "^0.4.19" | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ function SetupModel() { | |||||||
|         this.setupSyncFromDesktop(false); |         this.setupSyncFromDesktop(false); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.finish = () => { |     this.finish = async () => { | ||||||
|         if (this.setupNewDocument()) { |         if (this.setupNewDocument()) { | ||||||
|             const username = this.username(); |             const username = this.username(); | ||||||
|             const password1 = this.password1(); |             const password1 = this.password1(); | ||||||
| @@ -84,20 +84,33 @@ function SetupModel() { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             // not using server.js because it loads too many dependencies |             // not using server.js because it loads too many dependencies | ||||||
|             $.post('/api/setup/sync-from-server', { |             const resp = await $.post('/api/setup/sync-from-server', { | ||||||
|                 serverAddress: serverAddress, |                 serverAddress: serverAddress, | ||||||
|                 username: username, |                 username: username, | ||||||
|                 password: password |                 password: password | ||||||
|             }).then(() => { |  | ||||||
|                 window.location.replace("/"); |  | ||||||
|             }).catch((err) => { |  | ||||||
|                 alert("Error, see dev console for details."); |  | ||||||
|                 console.error(err); |  | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|  |             if (resp.result === 'success') { | ||||||
|  |                 this.step('sync-in-progress'); | ||||||
|  |  | ||||||
|  |                 checkOutstandingSyncs(); | ||||||
|  |  | ||||||
|  |                 setInterval(checkOutstandingSyncs, 1000); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 showAlert('Sync setup failed: ', resp.error); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function checkOutstandingSyncs() { | ||||||
|  |     const stats = await $.get('/api/sync/stats'); | ||||||
|  |     const totalOutstandingSyncs = stats.outstandingPushes + stats.outstandingPulls; | ||||||
|  |  | ||||||
|  |     $("#outstanding-syncs").html(totalOutstandingSyncs); | ||||||
|  | } | ||||||
|  |  | ||||||
| function showAlert(message) { | function showAlert(message) { | ||||||
|     $("#alert").html(message); |     $("#alert").html(message); | ||||||
|     $("#alert").show(); |     $("#alert").show(); | ||||||
|   | |||||||
| @@ -2,14 +2,10 @@ | |||||||
|  |  | ||||||
| const sqlInit = require('../../services/sql_init'); | const sqlInit = require('../../services/sql_init'); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const cls = require('../../services/cls'); | const rp = require('request-promise'); | ||||||
| const tmp = require('tmp-promise'); | const Option = require('../../entities/option'); | ||||||
| const http = require('http'); | const syncService = require('../../services/sync'); | ||||||
| const fs = require('fs'); |  | ||||||
| const log = require('../../services/log'); | const log = require('../../services/log'); | ||||||
| const DOCUMENT_PATH = require('../../services/data_dir').DOCUMENT_PATH; |  | ||||||
| const sourceIdService = require('../../services/source_id'); |  | ||||||
| const url = require('url'); |  | ||||||
|  |  | ||||||
| async function setupNewDocument(req) { | async function setupNewDocument(req) { | ||||||
|     const { username, password } = req.body; |     const { username, password } = req.body; | ||||||
| @@ -20,52 +16,44 @@ async function setupNewDocument(req) { | |||||||
| async function setupSyncFromServer(req) { | async function setupSyncFromServer(req) { | ||||||
|     const { serverAddress, username, password } = req.body; |     const { serverAddress, username, password } = req.body; | ||||||
|  |  | ||||||
|     const tempFile = await tmp.file(); |     try { | ||||||
|  |         log.info("Getting document options from sync server."); | ||||||
|  |  | ||||||
|     await new Promise((resolve, reject) => { |         // response is expected to contain documentId and documentSecret options | ||||||
|         const file = fs.createWriteStream(tempFile.path); |         const options = await rp.get({ | ||||||
|         const parsedAddress = url.parse(serverAddress); |             uri: serverAddress + '/api/sync/document', | ||||||
|  |             auth: { | ||||||
|  |                 'user': username, | ||||||
|  |                 'pass': password | ||||||
|  |             }, | ||||||
|  |             json: true | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         const options = { |         log.info("Creating database for sync"); | ||||||
|             method: 'GET', |  | ||||||
|             protocol: parsedAddress.protocol, |  | ||||||
|             host: parsedAddress.hostname, |  | ||||||
|             port: parsedAddress.port, |  | ||||||
|             path: '/api/sync/document', |  | ||||||
|             auth: username + ':' + password |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         log.info("Getting document from: " + serverAddress); |  | ||||||
|  |  | ||||||
|         http.request(options, function(response) { |  | ||||||
|             response.pipe(file); |  | ||||||
|  |  | ||||||
|             file.on('finish', function() { |  | ||||||
|                 log.info("Document download finished, closing & renaming."); |  | ||||||
|  |  | ||||||
|                 file.close(() => { // close() is async, call after close completes. |  | ||||||
|                     fs.rename(tempFile.path, DOCUMENT_PATH, async () => { |  | ||||||
|                         cls.reset(); |  | ||||||
|  |  | ||||||
|                         await sqlInit.initDbConnection(); |  | ||||||
|  |  | ||||||
|                         // we need to generate new source ID for this instance, otherwise it will |  | ||||||
|                         // match the original server one |  | ||||||
|         await sql.transactional(async () => { |         await sql.transactional(async () => { | ||||||
|                             await sourceIdService.generateSourceId(); |             await sqlInit.createDatabaseForSync(serverAddress); | ||||||
|  |  | ||||||
|  |             for (const opt of options) { | ||||||
|  |                 await new Option(opt).save(); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|                         resolve(); |         log.info("Triggering sync."); | ||||||
|                     }); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|         }).on('error', function(err) { // Handle errors |  | ||||||
|             fs.unlink(tempFile.path); // Delete the file async. (But we don't check the result) |  | ||||||
|  |  | ||||||
|             reject(err.message); |         // it's ok to not wait for it here | ||||||
|             log.error(err.message); |         syncService.sync(); | ||||||
|         }).end(); |  | ||||||
|     }); |         return { result: 'success' }; | ||||||
|  |     } | ||||||
|  |     catch (e) { | ||||||
|  |         log.error("Sync failed: " + e.message); | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |             result: 'failure', | ||||||
|  |             error: e.message | ||||||
|  |         }; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ const sql = require('../../services/sql'); | |||||||
| const optionService = require('../../services/options'); | const optionService = require('../../services/options'); | ||||||
| const contentHashService = require('../../services/content_hash'); | const contentHashService = require('../../services/content_hash'); | ||||||
| const log = require('../../services/log'); | const log = require('../../services/log'); | ||||||
| const DOCUMENT_PATH = require('../../services/data_dir').DOCUMENT_PATH; | const repository = require('../../services/repository'); | ||||||
|  |  | ||||||
| async function testSync() { | async function testSync() { | ||||||
|     try { |     try { | ||||||
| @@ -23,6 +23,10 @@ async function testSync() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function getStats() { | ||||||
|  |     return syncService.stats; | ||||||
|  | } | ||||||
|  |  | ||||||
| async function checkSync() { | async function checkSync() { | ||||||
|     return { |     return { | ||||||
|         hashes: await contentHashService.getHashes(), |         hashes: await contentHashService.getHashes(), | ||||||
| @@ -75,7 +79,10 @@ async function getChanged(req) { | |||||||
|  |  | ||||||
|     const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]); |     const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]); | ||||||
|  |  | ||||||
|     return await syncService.getSyncRecords(syncs); |     return { | ||||||
|  |         syncs: await syncService.getSyncRecords(syncs), | ||||||
|  |         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync') | ||||||
|  |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function update(req) { | async function update(req) { | ||||||
| @@ -87,10 +94,13 @@ async function update(req) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getDocument(req, resp) { | async function getDocument() { | ||||||
|     log.info("Serving document."); |     log.info("Serving document options."); | ||||||
|  |  | ||||||
|     resp.sendFile(DOCUMENT_PATH); |     return [ | ||||||
|  |         await repository.getOption('documentId'), | ||||||
|  |         await repository.getOption('documentSecret') | ||||||
|  |     ]; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
| @@ -102,5 +112,6 @@ module.exports = { | |||||||
|     forceNoteSync, |     forceNoteSync, | ||||||
|     getChanged, |     getChanged, | ||||||
|     update, |     update, | ||||||
|     getDocument |     getDocument, | ||||||
|  |     getStats | ||||||
| }; | }; | ||||||
| @@ -156,7 +156,8 @@ function register(app) { | |||||||
|     apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync); |     apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync); | ||||||
|     apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged); |     apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged); | ||||||
|     apiRoute(PUT, '/api/sync/update', syncApiRoute.update); |     apiRoute(PUT, '/api/sync/update', syncApiRoute.update); | ||||||
|     route(GET, '/api/sync/document', [auth.checkBasicAuth], syncApiRoute.getDocument); |     route(GET, '/api/sync/document', [auth.checkBasicAuth], syncApiRoute.getDocument, apiResultHandler); | ||||||
|  |     route(GET, '/api/sync/stats', [], syncApiRoute.getStats, apiResultHandler); | ||||||
|  |  | ||||||
|     apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog); |     apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 101; | const APP_DB_VERSION = 102; | ||||||
| const SYNC_VERSION = 1; | const SYNC_VERSION = 1; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ const repository = require('./repository'); | |||||||
| const protectedSessionService = require('./protected_session'); | const protectedSessionService = require('./protected_session'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
|  | let loaded = false; | ||||||
| let noteTitles; | let noteTitles; | ||||||
| let protectedNoteTitles; | let protectedNoteTitles; | ||||||
| let noteIds; | let noteIds; | ||||||
| @@ -34,6 +35,8 @@ async function load() { | |||||||
|     for (const noteId of hiddenLabels) { |     for (const noteId of hiddenLabels) { | ||||||
|         archived[noteId] = true; |         archived[noteId] = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     loaded = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| function findNotes(query) { | function findNotes(query) { | ||||||
| @@ -226,6 +229,10 @@ function getNotePath(noteId) { | |||||||
| } | } | ||||||
|  |  | ||||||
| eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { | eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId}) => { | ||||||
|  |     if (!loaded) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (entityName === 'notes') { |     if (entityName === 'notes') { | ||||||
|         const note = await repository.getNote(entityId); |         const note = await repository.getNote(entityId); | ||||||
|  |  | ||||||
| @@ -277,6 +284,10 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entityId | |||||||
| }); | }); | ||||||
|  |  | ||||||
| eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => { | eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => { | ||||||
|  |     if (!loaded) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); |     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); | ||||||
|  |  | ||||||
|     for (const noteId in protectedNoteTitles) { |     for (const noteId in protectedNoteTitles) { | ||||||
|   | |||||||
| @@ -5,21 +5,14 @@ const appInfo = require('./app_info'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
|  |  | ||||||
| async function initOptions(startNotePath, username, password) { | async function initDocumentOptions() { | ||||||
|     await optionService.createOption('documentId', utils.randomSecureToken(16), false); |     await optionService.createOption('documentId', utils.randomSecureToken(16), false); | ||||||
|     await optionService.createOption('documentSecret', utils.randomSecureToken(16), false); |     await optionService.createOption('documentSecret', utils.randomSecureToken(16), false); | ||||||
|  | } | ||||||
|  |  | ||||||
|     await optionService.createOption('startNotePath', startNotePath, false); | async function initSyncedOptions(username, password) { | ||||||
|     await optionService.createOption('protectedSessionTimeout', 600, true); |     await optionService.createOption('protectedSessionTimeout', 600); | ||||||
|     await optionService.createOption('noteRevisionSnapshotTimeInterval', 600, true); |     await optionService.createOption('noteRevisionSnapshotTimeInterval', 600); | ||||||
|     await optionService.createOption('lastBackupDate', dateUtils.nowDate(), false); |  | ||||||
|     await optionService.createOption('dbVersion', appInfo.dbVersion, false); |  | ||||||
|  |  | ||||||
|     await optionService.createOption('lastSyncedPull', appInfo.dbVersion, false); |  | ||||||
|     await optionService.createOption('lastSyncedPush', 0, false); |  | ||||||
|  |  | ||||||
|     await optionService.createOption('zoomFactor', 1.0, false); |  | ||||||
|     await optionService.createOption('theme', 'white', false); |  | ||||||
|  |  | ||||||
|     await optionService.createOption('username', username); |     await optionService.createOption('username', username); | ||||||
|  |  | ||||||
| @@ -34,12 +27,26 @@ async function initOptions(startNotePath, username, password) { | |||||||
|     await optionService.createOption('encryptedDataKeyIv', ''); |     await optionService.createOption('encryptedDataKeyIv', ''); | ||||||
|  |  | ||||||
|     await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); |     await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); | ||||||
|  | } | ||||||
|  |  | ||||||
|     await optionService.createOption('syncServerHost', '', false); | async function initNotSyncedOptions(startNotePath = '', syncServerHost = '') { | ||||||
|  |     await optionService.createOption('startNotePath', startNotePath, false); | ||||||
|  |     await optionService.createOption('lastBackupDate', dateUtils.nowDate(), false); | ||||||
|  |     await optionService.createOption('dbVersion', appInfo.dbVersion, false); | ||||||
|  |  | ||||||
|  |     await optionService.createOption('lastSyncedPull', appInfo.dbVersion, false); | ||||||
|  |     await optionService.createOption('lastSyncedPush', 0, false); | ||||||
|  |  | ||||||
|  |     await optionService.createOption('zoomFactor', 1.0, false); | ||||||
|  |     await optionService.createOption('theme', 'white', false); | ||||||
|  |  | ||||||
|  |     await optionService.createOption('syncServerHost', syncServerHost, false); | ||||||
|     await optionService.createOption('syncServerTimeout', 5000, false); |     await optionService.createOption('syncServerTimeout', 5000, false); | ||||||
|     await optionService.createOption('syncProxy', '', false); |     await optionService.createOption('syncProxy', '', false); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     initOptions |     initDocumentOptions, | ||||||
|  |     initSyncedOptions, | ||||||
|  |     initNotSyncedOptions | ||||||
| }; | }; | ||||||
| @@ -12,9 +12,13 @@ async function createConnection() { | |||||||
| } | } | ||||||
|  |  | ||||||
| let dbReadyResolve = null; | let dbReadyResolve = null; | ||||||
| const dbReady = new Promise((resolve, reject) => { | const dbReady = new Promise(async (resolve, reject) => { | ||||||
|     dbReadyResolve = resolve; |     dbReadyResolve = resolve; | ||||||
|  |  | ||||||
|  |     // no need to create new connection now since DB stays the same all the time | ||||||
|  |     const db = await createConnection(); | ||||||
|  |     sql.setDbConnection(db); | ||||||
|  |  | ||||||
|     initDbConnection(); |     initDbConnection(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -26,9 +30,6 @@ async function isDbInitialized() { | |||||||
|  |  | ||||||
| async function initDbConnection() { | async function initDbConnection() { | ||||||
|     await cls.init(async () => { |     await cls.init(async () => { | ||||||
|         const db = await createConnection(); |  | ||||||
|         sql.setDbConnection(db); |  | ||||||
|  |  | ||||||
|         await sql.execute("PRAGMA foreign_keys = ON"); |         await sql.execute("PRAGMA foreign_keys = ON"); | ||||||
|  |  | ||||||
|         if (!await isDbInitialized()) { |         if (!await isDbInitialized()) { | ||||||
| @@ -45,12 +46,12 @@ async function initDbConnection() { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         log.info("DB ready."); |         log.info("DB ready."); | ||||||
|         dbReadyResolve(db); |         dbReadyResolve(); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function createInitialDatabase(username, password) { | async function createInitialDatabase(username, password) { | ||||||
|     log.info("Connected to db, but schema doesn't exist. Initializing schema ..."); |     log.info("Creating initial database ..."); | ||||||
|  |  | ||||||
|     const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8'); |     const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8'); | ||||||
|     const notesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_notes.sql', 'UTF-8'); |     const notesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_notes.sql', 'UTF-8'); | ||||||
| @@ -67,15 +68,34 @@ async function createInitialDatabase(username, password) { | |||||||
|  |  | ||||||
|         const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition"); |         const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition"); | ||||||
|  |  | ||||||
|         await require('./options_init').initOptions(startNoteId, username, password); |         const optionsInitService = require('./options_init'); | ||||||
|  |  | ||||||
|  |         await optionsInitService.initDocumentOptions(); | ||||||
|  |         await optionsInitService.initSyncedOptions(username, password); | ||||||
|  |         await optionsInitService.initNotSyncedOptions(startNoteId); | ||||||
|  |  | ||||||
|         await require('./sync_table').fillAllSyncRows(); |         await require('./sync_table').fillAllSyncRows(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup."); |     log.info("Schema and initial content generated."); | ||||||
|  |  | ||||||
|     await initDbConnection(); |     await initDbConnection(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function createDatabaseForSync(syncServerHost) { | ||||||
|  |     log.info("Creating database for sync with server ..."); | ||||||
|  |  | ||||||
|  |     const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8'); | ||||||
|  |  | ||||||
|  |     await sql.transactional(async () => { | ||||||
|  |         await sql.executeScript(schema); | ||||||
|  |  | ||||||
|  |         await require('./options_init').initNotSyncedOptions('', syncServerHost); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     log.info("Schema and not synced options generated."); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function isDbUpToDate() { | async function isDbUpToDate() { | ||||||
|     const dbVersion = parseInt(await sql.getValue("SELECT value FROM options WHERE name = 'dbVersion'")); |     const dbVersion = parseInt(await sql.getValue("SELECT value FROM options WHERE name = 'dbVersion'")); | ||||||
|  |  | ||||||
| @@ -93,5 +113,6 @@ module.exports = { | |||||||
|     isDbInitialized, |     isDbInitialized, | ||||||
|     initDbConnection, |     initDbConnection, | ||||||
|     isDbUpToDate, |     isDbUpToDate, | ||||||
|     createInitialDatabase |     createInitialDatabase, | ||||||
|  |     createDatabaseForSync | ||||||
| }; | }; | ||||||
| @@ -18,6 +18,11 @@ const cls = require('./cls'); | |||||||
|  |  | ||||||
| let proxyToggle = true; | let proxyToggle = true; | ||||||
|  |  | ||||||
|  | const stats = { | ||||||
|  |     outstandingPushes: 0, | ||||||
|  |     outstandingPulls: 0 | ||||||
|  | }; | ||||||
|  |  | ||||||
| async function sync() { | async function sync() { | ||||||
|     try { |     try { | ||||||
|         await syncMutexService.doExclusively(async () => { |         await syncMutexService.doExclusively(async () => { | ||||||
| @@ -82,9 +87,18 @@ async function login() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function pullSync(syncContext) { | async function pullSync(syncContext) { | ||||||
|     const changesUri = '/api/sync/changed?lastSyncId=' + await getLastSyncedPull(); |     while (true) { | ||||||
|  |         const lastSyncedPull = await getLastSyncedPull(); | ||||||
|  |         const changesUri = '/api/sync/changed?lastSyncId=' + lastSyncedPull; | ||||||
|  |  | ||||||
|     const rows = await syncRequest(syncContext, 'GET', changesUri); |         const resp = await syncRequest(syncContext, 'GET', changesUri); | ||||||
|  |         stats.outstandingPulls = resp.maxSyncId - lastSyncedPull; | ||||||
|  |  | ||||||
|  |         const rows = resp.syncs; | ||||||
|  |  | ||||||
|  |         if (rows.length === 0) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         log.info("Pulled " + rows.length + " changes from " + changesUri); |         log.info("Pulled " + rows.length + " changes from " + changesUri); | ||||||
|  |  | ||||||
| @@ -96,8 +110,11 @@ async function pullSync(syncContext) { | |||||||
|                 await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId); |                 await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             stats.outstandingPulls = resp.maxSyncId - sync.id; | ||||||
|  |  | ||||||
|             await setLastSyncedPull(sync.id); |             await setLastSyncedPull(sync.id); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     log.info("Finished pull"); |     log.info("Finished pull"); | ||||||
| } | } | ||||||
| @@ -127,6 +144,8 @@ async function pushSync(syncContext) { | |||||||
|         if (filteredSyncs.length === 0) { |         if (filteredSyncs.length === 0) { | ||||||
|             log.info("Nothing to push"); |             log.info("Nothing to push"); | ||||||
|  |  | ||||||
|  |             stats.outstandingPushes = 0; | ||||||
|  |  | ||||||
|             await setLastSyncedPush(lastSyncedPush); |             await setLastSyncedPush(lastSyncedPush); | ||||||
|  |  | ||||||
|             break; |             break; | ||||||
| @@ -144,6 +163,8 @@ async function pushSync(syncContext) { | |||||||
|         lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id; |         lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id; | ||||||
|  |  | ||||||
|         await setLastSyncedPush(lastSyncedPush); |         await setLastSyncedPush(lastSyncedPush); | ||||||
|  |  | ||||||
|  |         stats.outstandingPushes = await sql.getValue(`SELECT MAX(id) FROM sync`) - lastSyncedPush; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -290,5 +311,6 @@ sqlInit.dbReady.then(async () => { | |||||||
| module.exports = { | module.exports = { | ||||||
|     sync, |     sync, | ||||||
|     login, |     login, | ||||||
|     getSyncRecords |     getSyncRecords, | ||||||
|  |     stats | ||||||
| }; | }; | ||||||
| @@ -86,6 +86,16 @@ | |||||||
|  |  | ||||||
|         <button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button> |         <button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |     <div data-bind="visible: step() == 'sync-in-progress'"> | ||||||
|  |         <h2>Sync in progress</h2> | ||||||
|  |  | ||||||
|  |         <div class="alert alert-success">Sync has been correctly set up. It will take some time for the initial sync to finish. Once it's done, you'll be redirected to the login page.</div> | ||||||
|  |  | ||||||
|  |         <div> | ||||||
|  |             Outstanding sync items: <strong id="outstanding-syncs">N/A</strong> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user