fix DB setup

This commit is contained in:
zadam 2020-06-20 21:42:41 +02:00
parent 6207203b35
commit 027afab6b1
19 changed files with 133 additions and 118 deletions

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const repository = require('../../services/repository'); const repository = require('../../services/repository');
const noteCacheService = require('../../services/note_cache/note_cache.js'); const noteCacheService = require('../../services/note_cache/note_cache_service');
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
const noteRevisionService = require('../../services/note_revisions'); const noteRevisionService = require('../../services/note_revisions');
const utils = require('../../services/utils'); const utils = require('../../services/utils');

View File

@ -146,7 +146,7 @@ function update(req) {
function syncFinished() { function syncFinished() {
// after first sync finishes, the application is ready to be used // after first sync finishes, the application is ready to be used
// this is meaningless but at the same time harmless (idempotent) for further syncs // this is meaningless but at the same time harmless (idempotent) for further syncs
sqlInit.dbInitialized(); sqlInit.setDbAsInitialized();
} }
function queueSector(req) { function queueSector(req) {

View File

@ -80,6 +80,8 @@ function apiRoute(method, path, routeHandler) {
function route(method, path, middleware, routeHandler, resultHandler, transactional = true) { function route(method, path, middleware, routeHandler, resultHandler, transactional = true) {
router[method](path, ...middleware, (req, res, next) => { router[method](path, ...middleware, (req, res, next) => {
const start = Date.now();
try { try {
cls.namespace.bindEmitter(req); cls.namespace.bindEmitter(req);
cls.namespace.bindEmitter(res); cls.namespace.bindEmitter(res);
@ -103,6 +105,12 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
res.sendStatus(500); res.sendStatus(500);
} }
const time = Date.now() - start;
if (time >= 10) {
console.log(`Slow request: ${time}ms - ${method} ${path}`);
}
}); });
} }

View File

@ -126,10 +126,12 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700); fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
} }
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000); setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000);
// kickoff first backup soon after start up // kickoff first backup soon after start up
setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000); setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000);
});
module.exports = { module.exports = {
backupNow, backupNow,

View File

@ -699,10 +699,12 @@ function runOnDemandChecks(autoFix) {
consistencyChecks.runChecks(); consistencyChecks.runChecks();
} }
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000); setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
// kickoff checks soon after startup (to not block the initial load) // kickoff checks soon after startup (to not block the initial load)
setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000); setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000);
});
module.exports = { module.exports = {
runOnDemandChecks runOnDemandChecks

View File

@ -9,4 +9,6 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
} }
}); });
sqlInit.dbReady.then(() => {
hoistedNote.setHoistedNoteId(optionService.getOption('hoistedNoteId')); hoistedNote.setHoistedNoteId(optionService.getOption('hoistedNoteId'));
});

View File

@ -3,23 +3,31 @@
const sql = require('../sql.js'); const sql = require('../sql.js');
const eventService = require('../events.js'); const eventService = require('../events.js');
const noteCache = require('./note_cache'); const noteCache = require('./note_cache');
const sqlInit = require('../sql_init');
const Note = require('./entities/note'); const Note = require('./entities/note');
const Branch = require('./entities/branch'); const Branch = require('./entities/branch');
const Attribute = require('./entities/attribute'); const Attribute = require('./entities/attribute');
sqlInit.dbReady.then(() => {
load();
});
function load() { function load() {
noteCache.reset(); noteCache.reset();
sql.getRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified, contentLength FROM notes WHERE isDeleted = 0`, []) for (const row of sql.iterateRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified, contentLength FROM notes WHERE isDeleted = 0`, [])) {
.map(row => new Note(noteCache, row)); new Note(noteCache, row);
}
sql.getRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, []) for (const row of sql.iterateRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, [])) {
.map(row => new Branch(noteCache, row)); new Branch(noteCache, row);
}
sql.getRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, []).map(row => new Attribute(noteCache, row)); for (const row of sql.iterateRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, [])) {
new Attribute(noteCache, row);
}
noteCache.loaded = true; noteCache.loaded = true;
noteCache.loadedResolve();
} }
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], ({entityName, entity}) => { eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], ({entityName, entity}) => {
@ -144,7 +152,5 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
}); });
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
noteCache.loadedPromise.then(() => noteCache.decryptProtectedNotes()); noteCache.decryptProtectedNotes();
}); });
load();

View File

@ -154,7 +154,7 @@ function getNotePath(noteId) {
return { return {
noteId: noteId, noteId: noteId,
branchId: getBranch(noteId, parentNote.noteId).branchId, branchId: noteCache.getBranch(noteId, parentNote.noteId).branchId,
title: noteTitle, title: noteTitle,
notePath: retPath, notePath: retPath,
path: retPath.join('/') path: retPath.join('/')

View File

@ -762,10 +762,12 @@ function duplicateNote(noteId, parentNoteId) {
}; };
} }
sqlInit.dbReady.then(() => {
// first cleanup kickoff 5 minutes after startup // first cleanup kickoff 5 minutes after startup
setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000); setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000); setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000);
});
module.exports = { module.exports = {
createNewNote, createNewNote,

View File

@ -1,6 +1,7 @@
const scriptService = require('./script'); const scriptService = require('./script');
const repository = require('./repository'); const repository = require('./repository');
const cls = require('./cls'); const cls = require('./cls');
const sqlInit = require('./sql_init');
function runNotesWithLabel(runAttrValue) { function runNotesWithLabel(runAttrValue) {
const notes = repository.getEntities(` const notes = repository.getEntities(`
@ -20,8 +21,10 @@ function runNotesWithLabel(runAttrValue) {
} }
} }
sqlInit.dbReady.then(() => {
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000); setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000); setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000); setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
});

View File

@ -24,7 +24,7 @@ function triggerSync() {
// it's ok to not wait for it here // it's ok to not wait for it here
syncService.sync().then(res => { syncService.sync().then(res => {
if (res.success) { if (res.success) {
sqlInit.dbInitialized(); sqlInit.setDbAsInitialized();
} }
}); });
} }

View File

@ -47,8 +47,10 @@ function isLocalSourceId(srcId) {
const currentSourceId = createSourceId(); const currentSourceId = createSourceId();
// this will also refresh source IDs // very ugly
cls.wrap(() => saveSourceId(currentSourceId)); setTimeout(() => {
sqlInit.dbReady.then(cls.wrap(() => saveSourceId(currentSourceId)));
}, 1000);
function getCurrentSourceId() { function getCurrentSourceId() {
return currentSourceId; return currentSourceId;

View File

@ -2,12 +2,11 @@
const log = require('./log'); const log = require('./log');
const cls = require('./cls'); const cls = require('./cls');
const Database = require('better-sqlite3');
const dataDir = require('./data_dir');
let dbConnection; const dbConnection = new Database(dataDir.DOCUMENT_PATH);
dbConnection.pragma('journal_mode = WAL');
function setDbConnection(connection) {
dbConnection = connection;
}
[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGTERM`].forEach(eventType => { [`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGTERM`].forEach(eventType => {
process.on(eventType, () => { process.on(eventType, () => {
@ -88,7 +87,7 @@ function rollback() {
} }
function getRow(query, params = []) { function getRow(query, params = []) {
return wrap(() => stmt(query).get(params), query); return wrap(query, s => s.get(params));
} }
function getRowOrNull(query, params = []) { function getRowOrNull(query, params = []) {
@ -135,7 +134,11 @@ function getManyRows(query, params) {
} }
function getRows(query, params = []) { function getRows(query, params = []) {
return wrap(() => stmt(query).all(params), query); return wrap(query, s => s.all(params));
}
function iterateRows(query, params = []) {
return stmt(query).iterate(params);
} }
function getMap(query, params = []) { function getMap(query, params = []) {
@ -171,7 +174,7 @@ function getColumn(query, params = []) {
function execute(query, params = []) { function execute(query, params = []) {
startTransactionIfNecessary(); startTransactionIfNecessary();
return wrap(() => stmt(query).run(params), query); return wrap(query, s => s.run(params));
} }
function executeWithoutTransaction(query, params = []) { function executeWithoutTransaction(query, params = []) {
@ -181,30 +184,23 @@ function executeWithoutTransaction(query, params = []) {
function executeMany(query, params) { function executeMany(query, params) {
startTransactionIfNecessary(); startTransactionIfNecessary();
// essentially just alias
getManyRows(query, params); getManyRows(query, params);
} }
function executeScript(query) { function executeScript(query) {
startTransactionIfNecessary(); startTransactionIfNecessary();
return wrap(() => stmt.run(query), query); return dbConnection.exec(query);
} }
function wrap(func, query) { function wrap(query, func) {
if (!dbConnection) {
throw new Error("DB connection not initialized yet");
}
const thisError = new Error();
try {
const startTimestamp = Date.now(); const startTimestamp = Date.now();
const result = func(dbConnection); const result = func(stmt(query));
const milliseconds = Date.now() - startTimestamp; const milliseconds = Date.now() - startTimestamp;
if (milliseconds >= 300) {
if (milliseconds >= 100) {
if (query.includes("WITH RECURSIVE")) { if (query.includes("WITH RECURSIVE")) {
log.info(`Slow recursive query took ${milliseconds}ms.`); log.info(`Slow recursive query took ${milliseconds}ms.`);
} }
@ -215,23 +211,12 @@ function wrap(func, query) {
return result; return result;
} }
catch (e) {
log.error("Error executing query. Inner exception: " + e.stack + thisError.stack);
thisError.message = e.stack;
throw thisError;
}
}
function startTransactionIfNecessary() { function startTransactionIfNecessary() {
if (!cls.get('isTransactional') if (!cls.get('isTransactional') || dbConnection.inTransaction) {
|| cls.get('isInTransaction')) {
return; return;
} }
cls.set('isInTransaction', true);
beginTransaction(); beginTransaction();
} }
@ -246,7 +231,7 @@ function transactional(func) {
try { try {
const ret = func(); const ret = func();
if (cls.get('isInTransaction')) { if (dbConnection.inTransaction) {
commit(); commit();
// note that sync rows sent from this action will be sent again by scheduled periodic ping // note that sync rows sent from this action will be sent again by scheduled periodic ping
@ -256,7 +241,7 @@ function transactional(func) {
return ret; return ret;
} }
catch (e) { catch (e) {
if (cls.get('isInTransaction')) { if (dbConnection.inTransaction) {
rollback(); rollback();
} }
@ -264,22 +249,17 @@ function transactional(func) {
} }
finally { finally {
cls.namespace.set('isTransactional', false); cls.namespace.set('isTransactional', false);
if (cls.namespace.get('isInTransaction')) {
cls.namespace.set('isInTransaction', false);
// resolving even for rollback since this is just semaphore for allowing another write transaction to proceed
}
} }
} }
module.exports = { module.exports = {
setDbConnection,
insert, insert,
replace, replace,
getValue, getValue,
getRow, getRow,
getRowOrNull, getRowOrNull,
getRows, getRows,
iterateRows,
getManyRows, getManyRows,
getMap, getMap,
getColumn, getColumn,

View File

@ -1,28 +1,21 @@
const log = require('./log'); const log = require('./log');
const dataDir = require('./data_dir');
const fs = require('fs'); const fs = require('fs');
const resourceDir = require('./resource_dir'); const resourceDir = require('./resource_dir');
const appInfo = require('./app_info'); const appInfo = require('./app_info');
const sql = require('./sql'); const sql = require('./sql');
const cls = require('./cls');
const utils = require('./utils'); const utils = require('./utils');
const optionService = require('./options'); const optionService = require('./options');
const port = require('./port'); const port = require('./port');
const Option = require('../entities/option'); const Option = require('../entities/option');
const TaskContext = require('./task_context.js'); const TaskContext = require('./task_context.js');
const Database = require('better-sqlite3');
const dbConnection = new Database(dataDir.DOCUMENT_PATH); const dbReady = utils.deferred();
dbConnection.pragma('journal_mode = WAL');
sql.setDbConnection(dbConnection); initDbConnection();
const dbReady = initDbConnection();
function schemaExists() { function schemaExists() {
const tableResults = sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'"); return !!sql.getValue(`SELECT name FROM sqlite_master
WHERE type = 'table' AND name = 'options'`);
return tableResults.length === 1;
} }
function isDbInitialized() { function isDbInitialized() {
@ -32,12 +25,10 @@ function isDbInitialized() {
const initialized = sql.getValue("SELECT value FROM options WHERE name = 'initialized'"); const initialized = sql.getValue("SELECT value FROM options WHERE name = 'initialized'");
// !initialized may be removed in the future, required only for migration return initialized === 'true';
return !initialized || initialized === 'true';
} }
function initDbConnection() { function initDbConnection() {
cls.init(() => {
if (!isDbInitialized()) { if (!isDbInitialized()) {
log.info(`DB not initialized, please visit setup page` + (utils.isElectron() ? '' : ` - http://[your-server-host]:${port} to see instructions on how to initialize Trilium.`)); log.info(`DB not initialized, please visit setup page` + (utils.isElectron() ? '' : ` - http://[your-server-host]:${port} to see instructions on how to initialize Trilium.`));
@ -60,7 +51,8 @@ function initDbConnection() {
} }
require('./options_init').initStartupOptions(); require('./options_init').initStartupOptions();
});
dbReady.resolve();
} }
function createInitialDatabase(username, password, theme) { function createInitialDatabase(username, password, theme) {
@ -156,7 +148,7 @@ function isDbUpToDate() {
return upToDate; return upToDate;
} }
function dbInitialized() { function setDbAsInitialized() {
if (!isDbInitialized()) { if (!isDbInitialized()) {
optionService.setOption('initialized', 'true'); optionService.setOption('initialized', 'true');
@ -174,5 +166,5 @@ module.exports = {
isDbUpToDate, isDbUpToDate,
createInitialDatabase, createInitialDatabase,
createDatabaseForSync, createDatabaseForSync,
dbInitialized setDbAsInitialized
}; };

View File

@ -368,12 +368,14 @@ function getMaxSyncId() {
return sql.getValue('SELECT MAX(id) FROM sync'); return sql.getValue('SELECT MAX(id) FROM sync');
} }
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(sync), 60000); setInterval(cls.wrap(sync), 60000);
// kickoff initial sync immediately // kickoff initial sync immediately
setTimeout(cls.wrap(sync), 3000); setTimeout(cls.wrap(sync), 3000);
setInterval(cls.wrap(updatePushStats), 1000); setInterval(cls.wrap(updatePushStats), 1000);
});
module.exports = { module.exports = {
sync, sync,

View File

@ -5,7 +5,6 @@ const repository = require('./repository');
const Branch = require('../entities/branch'); const Branch = require('../entities/branch');
const syncTableService = require('./sync_table'); const syncTableService = require('./sync_table');
const protectedSessionService = require('./protected_session'); const protectedSessionService = require('./protected_session');
const noteCacheService = require('./note_cache/note_cache.js');
function getNotes(noteIds) { function getNotes(noteIds) {
// we return also deleted notes which have been specifically asked for // we return also deleted notes which have been specifically asked for
@ -23,8 +22,6 @@ function getNotes(noteIds) {
protectedSessionService.decryptNotes(notes); protectedSessionService.decryptNotes(notes);
noteCacheService.loadedPromise;
notes.forEach(note => { notes.forEach(note => {
note.isProtected = !!note.isProtected note.isProtected = !!note.isProtected
}); });

View File

@ -259,6 +259,22 @@ function timeLimit(promise, limitMs) {
}); });
} }
function deferred() {
return (() => {
let resolve, reject;
let promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
})();
}
module.exports = { module.exports = {
randomSecureToken, randomSecureToken,
randomString, randomString,
@ -290,5 +306,6 @@ module.exports = {
getNoteTitle, getNoteTitle,
removeTextFileExtension, removeTextFileExtension,
formatDownloadTitle, formatDownloadTitle,
timeLimit timeLimit,
deferred
}; };

View File

@ -127,10 +127,10 @@ function closeSetupWindow() {
} }
} }
function registerGlobalShortcuts() { async function registerGlobalShortcuts() {
const {globalShortcut} = require('electron'); const {globalShortcut} = require('electron');
sqlInit.dbReady; await sqlInit.dbReady;
const allActions = keyboardActionsService.getKeyboardActions(); const allActions = keyboardActionsService.getKeyboardActions();