2017-10-24 22:58:59 -04:00
|
|
|
"use strict";
|
|
|
|
|
2018-04-01 21:27:46 -04:00
|
|
|
const syncService = require('../../services/sync');
|
|
|
|
const syncUpdateService = require('../../services/sync_update');
|
2020-08-02 23:27:48 +02:00
|
|
|
const entityChangesService = require('../../services/entity_changes.js');
|
2017-10-31 19:34:58 -04:00
|
|
|
const sql = require('../../services/sql');
|
2018-07-24 20:35:03 +02:00
|
|
|
const sqlInit = require('../../services/sql_init');
|
2018-04-01 21:27:46 -04:00
|
|
|
const optionService = require('../../services/options');
|
|
|
|
const contentHashService = require('../../services/content_hash');
|
2017-12-23 13:16:18 -05:00
|
|
|
const log = require('../../services/log');
|
2018-09-11 10:09:55 +02:00
|
|
|
const syncOptions = require('../../services/sync_options');
|
2019-11-13 22:32:14 +01:00
|
|
|
const dateUtils = require('../../services/date_utils');
|
2019-12-18 22:58:30 +01:00
|
|
|
const entityConstructor = require('../../entities/entity_constructor');
|
|
|
|
const utils = require('../../services/utils');
|
2017-11-21 22:11:27 -05:00
|
|
|
|
2020-11-15 20:50:24 +01:00
|
|
|
async function testSync() {
|
2018-07-23 10:29:17 +02:00
|
|
|
try {
|
2020-06-20 12:31:38 +02:00
|
|
|
if (!syncOptions.isSyncSetup()) {
|
2018-09-11 10:09:55 +02:00
|
|
|
return { success: false, message: "Sync server host is not configured. Please configure sync first." };
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:50:24 +01:00
|
|
|
await syncService.login();
|
2018-07-23 10:29:17 +02:00
|
|
|
|
2018-09-10 20:22:26 +02:00
|
|
|
// login was successful so we'll kick off sync now
|
|
|
|
// this is important in case when sync server has been just initialized
|
|
|
|
syncService.sync();
|
2018-09-10 20:05:10 +02:00
|
|
|
|
2018-09-10 20:22:26 +02:00
|
|
|
return { success: true, message: "Sync server handshake has been successful, sync has been started." };
|
2018-07-23 10:29:17 +02:00
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
return {
|
2018-09-10 20:05:10 +02:00
|
|
|
success: false,
|
2018-09-11 10:09:55 +02:00
|
|
|
message: e.message
|
2018-07-23 10:29:17 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function getStats() {
|
|
|
|
if (!sqlInit.schemaExists()) {
|
2018-07-25 09:46:57 +02:00
|
|
|
// fail silently but prevent errors from not existing options table
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2020-08-27 22:03:56 +02:00
|
|
|
const stats = {
|
2020-06-20 12:31:38 +02:00
|
|
|
initialized: optionService.getOption('initialized') === 'true',
|
2020-11-18 22:30:00 +01:00
|
|
|
outstandingPullCount: syncService.getOutstandingPullCount()
|
2018-07-24 08:12:36 +02:00
|
|
|
};
|
2020-08-27 22:03:56 +02:00
|
|
|
|
|
|
|
log.info(`Returning sync stats: ${JSON.stringify(stats)}`);
|
|
|
|
|
|
|
|
return stats;
|
2018-07-23 21:15:32 +02:00
|
|
|
}
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function checkSync() {
|
2018-03-30 14:27:41 -04:00
|
|
|
return {
|
2020-06-20 12:31:38 +02:00
|
|
|
entityHashes: contentHashService.getEntityHashes(),
|
2020-08-02 23:27:48 +02:00
|
|
|
maxEntityChangeId: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1')
|
2018-03-30 14:27:41 -04:00
|
|
|
};
|
|
|
|
}
|
2017-10-24 22:58:59 -04:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function syncNow() {
|
2020-06-13 10:23:36 +02:00
|
|
|
log.info("Received request to trigger sync now.");
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
return syncService.sync();
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-10-29 11:22:41 -04:00
|
|
|
|
2020-08-02 23:27:48 +02:00
|
|
|
function fillEntityChanges() {
|
|
|
|
entityChangesService.fillAllEntityChanges();
|
2017-12-19 22:04:51 -05:00
|
|
|
|
2017-12-23 13:16:18 -05:00
|
|
|
log.info("Sync rows have been filled.");
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-12-23 13:16:18 -05:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function forceFullSync() {
|
|
|
|
optionService.setOption('lastSyncedPull', 0);
|
|
|
|
optionService.setOption('lastSyncedPush', 0);
|
2017-12-13 23:03:48 -05:00
|
|
|
|
2017-12-23 13:16:18 -05:00
|
|
|
log.info("Forcing full sync.");
|
|
|
|
|
2017-12-13 23:03:48 -05:00
|
|
|
// not awaiting for the job to finish (will probably take a long time)
|
2018-04-01 21:27:46 -04:00
|
|
|
syncService.sync();
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-12-13 23:03:48 -05:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function forceNoteSync(req) {
|
2017-12-30 21:44:26 -05:00
|
|
|
const noteId = req.params.noteId;
|
|
|
|
|
2019-11-13 22:32:14 +01:00
|
|
|
const now = dateUtils.utcNowDateTime();
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
sql.execute(`UPDATE notes SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('notes', noteId);
|
2019-11-13 22:32:14 +01:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
sql.execute(`UPDATE note_contents SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('note_contents', noteId);
|
2017-12-30 21:44:26 -05:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
for (const branchId of sql.getColumn("SELECT branchId FROM branches WHERE noteId = ?", [noteId])) {
|
|
|
|
sql.execute(`UPDATE branches SET utcDateModified = ? WHERE branchId = ?`, [now, branchId]);
|
2019-11-13 22:32:14 +01:00
|
|
|
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('branches', branchId);
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-12-30 21:44:26 -05:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
for (const attributeId of sql.getColumn("SELECT attributeId FROM attributes WHERE noteId = ?", [noteId])) {
|
|
|
|
sql.execute(`UPDATE attributes SET utcDateModified = ? WHERE attributeId = ?`, [now, attributeId]);
|
2019-11-13 22:32:14 +01:00
|
|
|
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('attributes', attributeId);
|
2019-11-13 21:53:04 +01:00
|
|
|
}
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
for (const noteRevisionId of sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE noteId = ?", [noteId])) {
|
|
|
|
sql.execute(`UPDATE note_revisions SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('note_revisions', noteRevisionId);
|
2019-11-13 22:32:14 +01:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
sql.execute(`UPDATE note_revision_contents SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('note_revision_contents', noteRevisionId);
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-12-30 21:44:26 -05:00
|
|
|
|
2020-12-07 23:04:17 +01:00
|
|
|
entityChangesService.moveEntityChangeToTop('recent_changes', noteId);
|
2019-05-21 21:47:28 +02:00
|
|
|
|
2017-12-30 21:44:26 -05:00
|
|
|
log.info("Forcing note sync for " + noteId);
|
|
|
|
|
|
|
|
// not awaiting for the job to finish (will probably take a long time)
|
2018-04-01 21:27:46 -04:00
|
|
|
syncService.sync();
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-12-30 21:44:26 -05:00
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function getChanged(req) {
|
2020-04-04 14:57:19 +02:00
|
|
|
const startTime = Date.now();
|
|
|
|
|
2020-08-27 22:03:56 +02:00
|
|
|
const lastEntityChangeId = parseInt(req.query.lastEntityChangeId);
|
2017-10-24 22:58:59 -04:00
|
|
|
|
2020-08-02 23:27:48 +02:00
|
|
|
const entityChanges = sql.getRows("SELECT * FROM entity_changes WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastEntityChangeId]);
|
2018-04-07 21:53:42 -04:00
|
|
|
|
2020-04-04 14:57:19 +02:00
|
|
|
const ret = {
|
2020-08-02 23:43:39 +02:00
|
|
|
entityChanges: syncService.getEntityChangesRecords(entityChanges),
|
2020-08-02 23:27:48 +02:00
|
|
|
maxEntityChangeId: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1')
|
2018-07-23 21:15:32 +02:00
|
|
|
};
|
2020-04-04 14:57:19 +02:00
|
|
|
|
2020-08-02 23:43:39 +02:00
|
|
|
if (ret.entityChanges.length > 0) {
|
|
|
|
log.info(`Returning ${ret.entityChanges.length} entity changes in ${Date.now() - startTime}ms`);
|
2020-04-04 14:57:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2018-04-07 21:53:42 -04:00
|
|
|
}
|
|
|
|
|
2021-01-10 21:56:40 +01:00
|
|
|
const partialRequests = {};
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function update(req) {
|
2021-01-10 21:56:40 +01:00
|
|
|
let {body} = req;
|
|
|
|
|
|
|
|
const pageCount = parseInt(req.get('pageCount'));
|
|
|
|
const pageIndex = parseInt(req.get('pageIndex'));
|
|
|
|
|
|
|
|
if (pageCount !== 1) {
|
|
|
|
const requestId = req.get('requestId');
|
|
|
|
|
|
|
|
if (pageIndex === 0) {
|
|
|
|
partialRequests[requestId] = {
|
|
|
|
createdAt: Date.now(),
|
|
|
|
payload: ''
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!partialRequests[requestId]) {
|
|
|
|
throw new Error(`Partial request ${requestId}, index ${pageIndex} of ${pageCount} of pages does not have expected record.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
partialRequests[requestId].payload += req.body;
|
|
|
|
|
2021-01-11 22:48:51 +01:00
|
|
|
log.info(`Receiving partial request ${requestId}, page index ${pageIndex} out of ${pageCount} pages.`);
|
|
|
|
|
2021-01-10 21:56:40 +01:00
|
|
|
if (pageIndex !== pageCount - 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
body = JSON.parse(partialRequests[requestId].payload);
|
|
|
|
delete partialRequests[requestId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const {sourceId, entities} = body;
|
2018-04-07 21:53:42 -04:00
|
|
|
|
2020-08-27 22:11:17 +02:00
|
|
|
for (const {entityChange, entity} of entities) {
|
|
|
|
syncUpdateService.updateEntity(entityChange, entity, sourceId);
|
2018-04-07 21:53:42 -04:00
|
|
|
}
|
2018-03-30 14:27:41 -04:00
|
|
|
}
|
2017-10-26 21:16:21 -04:00
|
|
|
|
2021-01-10 21:56:40 +01:00
|
|
|
setInterval(() => {
|
|
|
|
for (const key in partialRequests) {
|
2021-01-11 22:48:51 +01:00
|
|
|
if (Date.now() - partialRequests[key].createdAt > 5 * 60 * 1000) {
|
|
|
|
log.info(`Cleaning up unfinished partial requests for ${key}`);
|
|
|
|
|
2021-01-10 21:56:40 +01:00
|
|
|
delete partialRequests[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 60 * 1000);
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function syncFinished() {
|
2018-07-24 20:35:03 +02:00
|
|
|
// after first sync finishes, the application is ready to be used
|
|
|
|
// this is meaningless but at the same time harmless (idempotent) for further syncs
|
2020-06-20 21:42:41 +02:00
|
|
|
sqlInit.setDbAsInitialized();
|
2018-07-24 20:35:03 +02:00
|
|
|
}
|
|
|
|
|
2020-06-20 12:31:38 +02:00
|
|
|
function queueSector(req) {
|
2019-12-18 22:58:30 +01:00
|
|
|
const entityName = utils.sanitizeSqlIdentifier(req.params.entityName);
|
|
|
|
const sector = utils.sanitizeSqlIdentifier(req.params.sector);
|
|
|
|
|
2021-04-04 22:02:40 +02:00
|
|
|
entityChangesService.addEntityChangesForSector(entityName, sector);
|
2019-12-18 22:58:30 +01:00
|
|
|
}
|
|
|
|
|
2018-03-30 14:27:41 -04:00
|
|
|
module.exports = {
|
2018-07-23 10:29:17 +02:00
|
|
|
testSync,
|
2018-03-30 14:27:41 -04:00
|
|
|
checkSync,
|
|
|
|
syncNow,
|
2020-08-02 23:27:48 +02:00
|
|
|
fillEntityChanges,
|
2018-03-30 14:27:41 -04:00
|
|
|
forceFullSync,
|
|
|
|
forceNoteSync,
|
|
|
|
getChanged,
|
2018-07-22 19:56:20 +02:00
|
|
|
update,
|
2018-07-24 20:35:03 +02:00
|
|
|
getStats,
|
2019-12-18 22:58:30 +01:00
|
|
|
syncFinished,
|
|
|
|
queueSector
|
2020-06-13 10:23:36 +02:00
|
|
|
};
|