synchronization of transactions using promise so only one can be active

This commit is contained in:
azivner 2017-11-28 17:24:08 -05:00
parent fd764f6163
commit 3d84f5c3b0
20 changed files with 194 additions and 180 deletions

View File

@ -12,10 +12,10 @@ const treeChanges = (function() {
if (changeInPath) { if (changeInPath) {
recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); recentNotes.removeRecentNote(noteTree.getCurrentNotePath());
}
noteTree.setCurrentNotePathToHash(node); noteTree.setCurrentNotePathToHash(node);
} }
}
async function moveAfterNode(node, afterNode, changeInPath = true) { async function moveAfterNode(node, afterNode, changeInPath = true) {
await $.ajax({ await $.ajax({
@ -28,10 +28,10 @@ const treeChanges = (function() {
if (changeInPath) { if (changeInPath) {
recentNotes.removeRecentNote(noteTree.getCurrentNotePath()); recentNotes.removeRecentNote(noteTree.getCurrentNotePath());
}
noteTree.setCurrentNotePathToHash(node); noteTree.setCurrentNotePathToHash(node);
} }
}
// beware that first arg is noteId and second is noteTreeId! // beware that first arg is noteId and second is noteTreeId!
async function cloneNoteAfter(noteId, afterNoteTreeId) { async function cloneNoteAfter(noteId, afterNoteTreeId) {

View File

@ -16,8 +16,8 @@ async function deleteOld() {
const cutoffId = await sql.getSingleValue("SELECT id FROM event_log ORDER BY id DESC LIMIT 1000, 1"); const cutoffId = await sql.getSingleValue("SELECT id FROM event_log ORDER BY id DESC LIMIT 1000, 1");
if (cutoffId) { if (cutoffId) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, "DELETE FROM event_log WHERE id < ?", [cutoffId]); await sql.execute("DELETE FROM event_log WHERE id < ?", [cutoffId]);
}); });
} }
} }

View File

@ -25,10 +25,10 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => {
}); });
router.put('', auth.checkApiAuth, async (req, res, next) => { router.put('', auth.checkApiAuth, async (req, res, next) => {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.replace(db, "notes_history", req.body); await sql.replace("notes_history", req.body);
await sync_table.addNoteHistorySync(db, req.body.note_history_id); await sync_table.addNoteHistorySync(req.body.note_history_id);
}); });
res.send(); res.send();

View File

@ -59,8 +59,8 @@ router.put('/:noteId', async (req, res, next) => {
}); });
router.delete('/:noteTreeId', async (req, res, next) => { router.delete('/:noteTreeId', async (req, res, next) => {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await notes.deleteNote(db, req.params.noteTreeId); await notes.deleteNote(req.params.noteTreeId);
}); });
res.send({}); res.send({});

View File

@ -16,11 +16,11 @@ router.put('/:noteTreeId/moveTo/:parentNoteId', auth.checkApiAuth, async (req, r
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, "UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?", await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[parentNoteId, newNotePos, now, noteTreeId]); [parentNoteId, newNotePos, now, noteTreeId]);
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
}); });
res.send({}); res.send({});
@ -33,18 +33,18 @@ router.put('/:noteTreeId/moveBefore/:beforeNoteTreeId', async (req, res, next) =
const beforeNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [beforeNoteTreeId]); const beforeNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [beforeNoteTreeId]);
if (beforeNote) { if (beforeNote) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
// we don't change date_modified so other changes are prioritized in case of conflict // we don't change date_modified so other changes are prioritized in case of conflict
await sql.execute(db, "UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos >= ? AND is_deleted = 0", await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos >= ? AND is_deleted = 0",
[beforeNote.note_pid, beforeNote.note_pos]); [beforeNote.note_pid, beforeNote.note_pos]);
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.execute(db, "UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?", await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[beforeNote.note_pid, beforeNote.note_pos, now, noteTreeId]); [beforeNote.note_pid, beforeNote.note_pos, now, noteTreeId]);
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteReorderingSync(db, beforeNote.note_pid); await sync_table.addNoteReorderingSync(beforeNote.note_pid);
}); });
res.send({}); res.send({});
@ -61,18 +61,18 @@ router.put('/:noteTreeId/moveAfter/:afterNoteTreeId', async (req, res, next) =>
const afterNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [afterNoteTreeId]); const afterNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [afterNoteTreeId]);
if (afterNote) { if (afterNote) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
// we don't change date_modified so other changes are prioritized in case of conflict // we don't change date_modified so other changes are prioritized in case of conflict
await sql.execute(db, "UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0", await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0",
[afterNote.note_pid, afterNote.note_pos]); [afterNote.note_pid, afterNote.note_pos]);
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.execute(db, "UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?", await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[afterNote.note_pid, afterNote.note_pos + 1, now, noteTreeId]); [afterNote.note_pid, afterNote.note_pos + 1, now, noteTreeId]);
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteReorderingSync(db, afterNote.note_pid); await sync_table.addNoteReorderingSync(afterNote.note_pid);
}); });
res.send({}); res.send({});
@ -105,7 +105,7 @@ router.put('/:childNoteId/cloneTo/:parentNoteId', auth.checkApiAuth, async (req,
const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]); const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]);
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 1; const newNotePos = maxNotePos === null ? 0 : maxNotePos + 1;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
const noteTree = { const noteTree = {
'note_tree_id': utils.newNoteTreeId(), 'note_tree_id': utils.newNoteTreeId(),
'note_id': childNoteId, 'note_id': childNoteId,
@ -116,9 +116,9 @@ router.put('/:childNoteId/cloneTo/:parentNoteId', auth.checkApiAuth, async (req,
'is_deleted': 0 'is_deleted': 0
}; };
await sql.replace(db, "notes_tree", noteTree); await sql.replace("notes_tree", noteTree);
await sync_table.addNoteTreeSync(db, noteTree.note_tree_id); await sync_table.addNoteTreeSync(noteTree.note_tree_id);
res.send({ res.send({
success: true success: true
@ -152,9 +152,9 @@ router.put('/:noteId/cloneAfter/:afterNoteTreeId', async (req, res, next) => {
}); });
} }
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
// we don't change date_modified so other changes are prioritized in case of conflict // we don't change date_modified so other changes are prioritized in case of conflict
await sql.execute(db, "UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0", await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0",
[afterNote.note_pid, afterNote.note_pos]); [afterNote.note_pid, afterNote.note_pos]);
const noteTree = { const noteTree = {
@ -167,10 +167,10 @@ router.put('/:noteId/cloneAfter/:afterNoteTreeId', async (req, res, next) => {
'is_deleted': 0 'is_deleted': 0
}; };
await sql.replace(db, "notes_tree", noteTree); await sql.replace("notes_tree", noteTree);
await sync_table.addNoteTreeSync(db, noteTree.note_tree_id); await sync_table.addNoteTreeSync(noteTree.note_tree_id);
await sync_table.addNoteReorderingSync(db, afterNote.note_pid); await sync_table.addNoteReorderingSync(afterNote.note_pid);
res.send({ res.send({
success: true success: true
@ -202,8 +202,8 @@ router.put('/:noteTreeId/expanded/:expanded', async (req, res, next) => {
const noteTreeId = req.params.noteTreeId; const noteTreeId = req.params.noteTreeId;
const expanded = req.params.expanded; const expanded = req.params.expanded;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, "UPDATE notes_tree SET is_expanded = ? WHERE note_tree_id = ?", [expanded, noteTreeId]); await sql.execute("UPDATE notes_tree SET is_expanded = ? WHERE note_tree_id = ?", [expanded, noteTreeId]);
}); });
res.send({}); res.send({});

View File

@ -15,16 +15,16 @@ router.get('', auth.checkApiAuth, async (req, res, next) => {
router.put('/:notePath', auth.checkApiAuth, async (req, res, next) => { router.put('/:notePath', auth.checkApiAuth, async (req, res, next) => {
const notePath = req.params.notePath; const notePath = req.params.notePath;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.replace(db, 'recent_notes', { await sql.replace('recent_notes', {
note_path: notePath, note_path: notePath,
date_accessed: utils.nowTimestamp(), date_accessed: utils.nowTimestamp(),
is_deleted: 0 is_deleted: 0
}); });
await sync_table.addRecentNoteSync(db, notePath); await sync_table.addRecentNoteSync(notePath);
await options.setOption(db, 'start_note_tree_id', notePath); await options.setOption('start_note_tree_id', notePath);
}); });
res.send(await getRecentNotes()); res.send(await getRecentNotes());
@ -33,10 +33,10 @@ router.put('/:notePath', auth.checkApiAuth, async (req, res, next) => {
router.delete('/:notePath', auth.checkApiAuth, async (req, res, next) => { router.delete('/:notePath', auth.checkApiAuth, async (req, res, next) => {
const notePath = req.params.notePath; const notePath = req.params.notePath;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, 'UPDATE recent_notes SET is_deleted = 1 WHERE note_path = ?', [notePath]); await sql.execute('UPDATE recent_notes SET is_deleted = 1 WHERE note_path = ?', [notePath]);
await sync_table.addRecentNoteSync(db, notePath); await sync_table.addRecentNoteSync(notePath);
}); });
res.send(await getRecentNotes()); res.send(await getRecentNotes());
@ -52,8 +52,8 @@ async function deleteOld() {
const cutoffDateAccessed = await sql.getSingleValue("SELECT date_accessed FROM recent_notes WHERE is_deleted = 0 ORDER BY date_accessed DESC LIMIT 100, 1"); const cutoffDateAccessed = await sql.getSingleValue("SELECT date_accessed FROM recent_notes WHERE is_deleted = 0 ORDER BY date_accessed DESC LIMIT 100, 1");
if (cutoffDateAccessed) { if (cutoffDateAccessed) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, "DELETE FROM recent_notes WHERE date_accessed < ?", [cutoffDateAccessed]); await sql.execute("DELETE FROM recent_notes WHERE date_accessed < ?", [cutoffDateAccessed]);
}); });
} }
} }

View File

@ -29,8 +29,8 @@ router.post('/', async (req, res, next) => {
if (ALLOWED_OPTIONS.includes(body['name'])) { if (ALLOWED_OPTIONS.includes(body['name'])) {
const optionName = await options.getOption(body['name']); const optionName = await options.getOption(body['name']);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await options.setOption(db, body['name'], body['value']); await options.setOption(body['name'], body['value']);
}); });
res.send({}); res.send({});

View File

@ -41,8 +41,8 @@ router.put('/:noteId/protectSubTree/:isProtected', auth.checkApiAuth, async (req
const isProtected = !!parseInt(req.params.isProtected); const isProtected = !!parseInt(req.params.isProtected);
const dataKey = protected_session.getDataKey(req); const dataKey = protected_session.getDataKey(req);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await notes.protectNoteRecursively(db, noteId, dataKey, isProtected); await notes.protectNoteRecursively(noteId, dataKey, isProtected);
}); });
res.send({}); res.send({});
@ -52,10 +52,10 @@ router.put('/:noteTreeId/setPrefix', auth.checkApiAuth, async (req, res, next) =
const noteTreeId = req.params.noteTreeId; const noteTreeId = req.params.noteTreeId;
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix; const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.execute(db, "UPDATE notes_tree SET prefix = ?, date_modified = ? WHERE note_tree_id = ?", [prefix, utils.nowTimestamp(), noteTreeId]); await sql.execute("UPDATE notes_tree SET prefix = ?, date_modified = ? WHERE note_tree_id = ?", [prefix, utils.nowTimestamp(), noteTreeId]);
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
}); });
res.send({}); res.send({});

View File

@ -28,8 +28,8 @@ async function backupNow() {
log.info("Created backup at " + backupFile); log.info("Created backup at " + backupFile);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await options.setOption(db, 'last_backup_date', now); await options.setOption('last_backup_date', now);
}); });
} }

View File

@ -17,10 +17,10 @@ async function changePassword(currentPassword, newPassword, req) {
const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword)); const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword));
const decryptedDataKey = await password_encryption.getDataKey(currentPassword); const decryptedDataKey = await password_encryption.getDataKey(currentPassword);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await password_encryption.setDataKey(db, newPassword, decryptedDataKey); await password_encryption.setDataKey(newPassword, decryptedDataKey);
await options.setOption(db, 'password_verification_hash', newPasswordVerificationKey); await options.setOption('password_verification_hash', newPasswordVerificationKey);
}); });
return { return {

View File

@ -2,12 +2,12 @@ const sql = require('./sql');
const utils = require('./utils'); const utils = require('./utils');
const log = require('./log'); const log = require('./log');
async function addEvent(db, comment) { async function addEvent(comment) {
await addNoteEvent(db, null, comment); await addNoteEvent(null, comment);
} }
async function addNoteEvent(db, noteId, comment) { async function addNoteEvent(noteId, comment) {
await sql.insert(db, 'event_log', { await sql.insert('event_log', {
note_id : noteId, note_id : noteId,
comment: comment, comment: comment,
date_added: utils.nowTimestamp() date_added: utils.nowTimestamp()

View File

@ -43,13 +43,13 @@ async function migrate() {
try { try {
log.info("Attempting migration to version " + mig.dbVersion); log.info("Attempting migration to version " + mig.dbVersion);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (mig.type === 'sql') { if (mig.type === 'sql') {
const migrationSql = fs.readFileSync(MIGRATIONS_DIR + "/" + mig.file).toString('utf8'); const migrationSql = fs.readFileSync(MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
console.log("Migration with SQL script: " + migrationSql); console.log("Migration with SQL script: " + migrationSql);
await sql.executeScript(db, migrationSql); await sql.executeScript(migrationSql);
} }
else if (mig.type === 'js') { else if (mig.type === 'js') {
console.log("Migration with JS module"); console.log("Migration with JS module");
@ -61,7 +61,7 @@ async function migrate() {
throw new Error("Unknown migration type " + mig.type); throw new Error("Unknown migration type " + mig.type);
} }
await options.setOption(db, "db_version", mig.dbVersion); await options.setOption("db_version", mig.dbVersion);
}); });
log.info("Migration to version " + mig.dbVersion + " has been successful."); log.info("Migration to version " + mig.dbVersion + " has been successful.");

View File

@ -11,7 +11,7 @@ async function createNewNote(parentNoteId, note) {
let newNotePos = 0; let newNotePos = 0;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (note.target === 'into') { if (note.target === 'into') {
const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]); const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]);
@ -22,19 +22,19 @@ async function createNewNote(parentNoteId, note) {
newNotePos = afterNote.note_pos + 1; newNotePos = afterNote.note_pos + 1;
await sql.execute(db, 'UPDATE notes_tree SET note_pos = note_pos + 1, date_modified = ? WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0', await sql.execute('UPDATE notes_tree SET note_pos = note_pos + 1, date_modified = ? WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0',
[utils.nowTimestamp(), parentNoteId, afterNote.note_pos]); [utils.nowTimestamp(), parentNoteId, afterNote.note_pos]);
} }
else { else {
throw new Error('Unknown target: ' + note.target); throw new Error('Unknown target: ' + note.target);
} }
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteSync(db, noteId); await sync_table.addNoteSync(noteId);
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.insert(db, "notes", { await sql.insert("notes", {
'note_id': noteId, 'note_id': noteId,
'note_title': note.note_title, 'note_title': note.note_title,
'note_text': '', 'note_text': '',
@ -43,7 +43,7 @@ async function createNewNote(parentNoteId, note) {
'is_protected': note.is_protected 'is_protected': note.is_protected
}); });
await sql.insert(db, "notes_tree", { await sql.insert("notes_tree", {
'note_tree_id': noteTreeId, 'note_tree_id': noteTreeId,
'note_id': noteId, 'note_id': noteId,
'note_pid': parentNoteId, 'note_pid': parentNoteId,
@ -65,10 +65,10 @@ async function encryptNote(note, ctx) {
note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), data_encryption.noteTextIv(note.detail.note_id), note.detail.note_text); note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), data_encryption.noteTextIv(note.detail.note_id), note.detail.note_text);
} }
async function protectNoteRecursively(db, noteId, dataKey, protect) { async function protectNoteRecursively(noteId, dataKey, protect) {
const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]); const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]);
await protectNote(db, note, dataKey, protect); await protectNote(note, dataKey, protect);
const children = await sql.getFlattenedResults("note_id", "SELECT note_id FROM notes_tree WHERE note_pid = ?", [noteId]); const children = await sql.getFlattenedResults("note_id", "SELECT note_id FROM notes_tree WHERE note_pid = ?", [noteId]);
@ -77,7 +77,7 @@ async function protectNoteRecursively(db, noteId, dataKey, protect) {
} }
} }
async function protectNote(db, note, dataKey, protect) { async function protectNote(note, dataKey, protect) {
let changed = false; let changed = false;
if (protect && !note.is_protected) { if (protect && !note.is_protected) {
@ -98,16 +98,16 @@ async function protectNote(db, note, dataKey, protect) {
if (changed) { if (changed) {
console.log("Updating..."); console.log("Updating...");
await sql.execute(db, "UPDATE notes SET note_title = ?, note_text = ?, is_protected = ? WHERE note_id = ?", await sql.execute("UPDATE notes SET note_title = ?, note_text = ?, is_protected = ? WHERE note_id = ?",
[note.note_title, note.note_text, note.is_protected, note.note_id]); [note.note_title, note.note_text, note.is_protected, note.note_id]);
await sync_table.addNoteSync(db, note.note_id); await sync_table.addNoteSync(note.note_id);
} }
await protectNoteHistory(db, note.note_id, dataKey, protect); await protectNoteHistory(note.note_id, dataKey, protect);
} }
async function protectNoteHistory(db, noteId, dataKey, protect) { async function protectNoteHistory(noteId, dataKey, protect) {
const historyToChange = await sql.getResults("SELECT * FROM notes_history WHERE note_id = ? AND is_protected != ?", [noteId, protect]); const historyToChange = await sql.getResults("SELECT * FROM notes_history WHERE note_id = ? AND is_protected != ?", [noteId, protect]);
for (const history of historyToChange) { for (const history of historyToChange) {
@ -122,10 +122,10 @@ async function protectNoteHistory(db, noteId, dataKey, protect) {
history.is_protected = false; history.is_protected = false;
} }
await sql.execute(db, "UPDATE notes_history SET note_title = ?, note_text = ?, is_protected = ? WHERE note_history_id = ?", await sql.execute("UPDATE notes_history SET note_title = ?, note_text = ?, is_protected = ? WHERE note_history_id = ?",
[history.note_title, history.note_text, history.is_protected, history.note_history_id]); [history.note_title, history.note_text, history.is_protected, history.note_history_id]);
await sync_table.addNoteHistorySync(db, history.note_history_id); await sync_table.addNoteHistorySync(history.note_history_id);
} }
} }
@ -147,11 +147,11 @@ async function updateNote(noteId, newNote, ctx) {
const existingNoteHistoryId = await sql.getSingleValue("SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_from >= ?", [noteId, historyCutoff]); const existingNoteHistoryId = await sql.getSingleValue("SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_from >= ?", [noteId, historyCutoff]);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (!existingNoteHistoryId) { if (!existingNoteHistoryId) {
const newNoteHistoryId = utils.newNoteHistoryId(); const newNoteHistoryId = utils.newNoteHistoryId();
await sql.insert(db, 'notes_history', { await sql.insert('notes_history', {
note_history_id: newNoteHistoryId, note_history_id: newNoteHistoryId,
note_id: noteId, note_id: noteId,
note_title: noteTitleForHistory, note_title: noteTitleForHistory,
@ -161,53 +161,53 @@ async function updateNote(noteId, newNote, ctx) {
date_modified_to: now date_modified_to: now
}); });
await sync_table.addNoteHistorySync(db, newNoteHistoryId); await sync_table.addNoteHistorySync(newNoteHistoryId);
} }
await protectNoteHistory(db, noteId, ctx.getDataKeyOrNull(), newNote.detail.is_protected); await protectNoteHistory(noteId, ctx.getDataKeyOrNull(), newNote.detail.is_protected);
await sql.execute(db, "UPDATE notes SET note_title = ?, note_text = ?, is_protected = ?, date_modified = ? WHERE note_id = ?", [ await sql.execute("UPDATE notes SET note_title = ?, note_text = ?, is_protected = ?, date_modified = ? WHERE note_id = ?", [
newNote.detail.note_title, newNote.detail.note_title,
newNote.detail.note_text, newNote.detail.note_text,
newNote.detail.is_protected, newNote.detail.is_protected,
now, now,
noteId]); noteId]);
await sql.remove(db, "images", noteId); await sql.remove("images", noteId);
for (const img of newNote.images) { for (const img of newNote.images) {
img.image_data = atob(img.image_data); img.image_data = atob(img.image_data);
await sql.insert(db, "images", img); await sql.insert("images", img);
} }
await sql.remove(db, "links", noteId); await sql.remove("links", noteId);
for (const link in newNote.links) { for (const link in newNote.links) {
//await sql.insert(db, "links", link); //await sql.insert("links", link);
} }
await sync_table.addNoteSync(db, noteId); await sync_table.addNoteSync(noteId);
}); });
} }
async function deleteNote(db, noteTreeId) { async function deleteNote(noteTreeId) {
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.execute(db, "UPDATE notes_tree SET is_deleted = 1, date_modified = ? WHERE note_tree_id = ?", [now, noteTreeId]); await sql.execute("UPDATE notes_tree SET is_deleted = 1, date_modified = ? WHERE note_tree_id = ?", [now, noteTreeId]);
await sync_table.addNoteTreeSync(db, noteTreeId); await sync_table.addNoteTreeSync(noteTreeId);
const noteId = await sql.getSingleValue("SELECT note_id FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]); const noteId = await sql.getSingleValue("SELECT note_id FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]);
const notDeletedNoteTreesCount = await sql.getSingleValue("SELECT COUNT(*) FROM notes_tree WHERE note_id = ? AND is_deleted = 0", [noteId]); const notDeletedNoteTreesCount = await sql.getSingleValue("SELECT COUNT(*) FROM notes_tree WHERE note_id = ? AND is_deleted = 0", [noteId]);
if (!notDeletedNoteTreesCount) { if (!notDeletedNoteTreesCount) {
await sql.execute(db, "UPDATE notes SET is_deleted = 1, date_modified = ? WHERE note_id = ?", [now, noteId]); await sql.execute("UPDATE notes SET is_deleted = 1, date_modified = ? WHERE note_id = ?", [now, noteId]);
await sync_table.addNoteSync(db, noteId); await sync_table.addNoteSync(noteId);
const children = await sql.getResults("SELECT note_tree_id FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [noteId]); const children = await sql.getResults("SELECT note_tree_id FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [noteId]);
for (const child of children) { for (const child of children) {
await deleteNote(db, child.note_tree_id); await deleteNote(child.note_tree_id);
} }
} }
} }

View File

@ -15,25 +15,25 @@ async function getOption(optName) {
return row['opt_value']; return row['opt_value'];
} }
async function setOption(db, optName, optValue) { async function setOption(optName, optValue) {
if (SYNCED_OPTIONS.includes(optName)) { if (SYNCED_OPTIONS.includes(optName)) {
await sync_table.addOptionsSync(optName); await sync_table.addOptionsSync(optName);
} }
await setOptionNoSync(db, optName, optValue); await setOptionNoSync(optName, optValue);
} }
async function setOptionNoSync(db, optName, optValue) { async function setOptionNoSync(optName, optValue) {
const now = utils.nowTimestamp(); const now = utils.nowTimestamp();
await sql.execute(db, "UPDATE options SET opt_value = ?, date_modified = ? WHERE opt_name = ?", [optValue, now, optName]); await sql.execute("UPDATE options SET opt_value = ?, date_modified = ? WHERE opt_name = ?", [optValue, now, optName]);
} }
sql.dbReady.then(async () => { sql.dbReady.then(async () => {
if (!await getOption('document_id') || !await getOption('document_secret')) { if (!await getOption('document_id') || !await getOption('document_secret')) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await setOption(db, 'document_id', utils.randomSecureToken(16)); await setOption('document_id', utils.randomSecureToken(16));
await setOption(db, 'document_secret', utils.randomSecureToken(16)); await setOption('document_secret', utils.randomSecureToken(16));
}); });
} }
}); });

View File

@ -11,18 +11,18 @@ async function verifyPassword(password) {
return givenPasswordHash === dbPasswordHash; return givenPasswordHash === dbPasswordHash;
} }
async function setDataKey(db, password, plainTextDataKey) { async function setDataKey(password, plainTextDataKey) {
const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password);
const encryptedDataKeyIv = utils.randomSecureToken(16).slice(0, 16); const encryptedDataKeyIv = utils.randomSecureToken(16).slice(0, 16);
await options.setOption(db, 'encrypted_data_key_iv', encryptedDataKeyIv); await options.setOption('encrypted_data_key_iv', encryptedDataKeyIv);
const buffer = Buffer.from(plainTextDataKey); const buffer = Buffer.from(plainTextDataKey);
const newEncryptedDataKey = data_encryption.encrypt(passwordDerivedKey, encryptedDataKeyIv, buffer); const newEncryptedDataKey = data_encryption.encrypt(passwordDerivedKey, encryptedDataKeyIv, buffer);
await options.setOption(db, 'encrypted_data_key', newEncryptedDataKey); await options.setOption('encrypted_data_key', newEncryptedDataKey);
} }
async function getDataKey(password) { async function getDataKey(password) {

View File

@ -10,8 +10,8 @@ let allSourceIds = [];
sql.dbReady.then(async () => { sql.dbReady.then(async () => {
try { try {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.insert(db, "source_ids", { await sql.insert("source_ids", {
source_id: currentSourceId, source_id: currentSourceId,
date_created: utils.nowTimestamp() date_created: utils.nowTimestamp()
}); });

View File

@ -10,7 +10,7 @@ async function createConnection() {
const dbReady = createConnection(); const dbReady = createConnection();
async function insert(db, table_name, rec, replace = false) { async function insert(table_name, rec, replace = false) {
const keys = Object.keys(rec); const keys = Object.keys(rec);
if (keys.length === 0) { if (keys.length === 0) {
log.error("Can't insert empty object into table " + table_name); log.error("Can't insert empty object into table " + table_name);
@ -22,36 +22,36 @@ async function insert(db, table_name, rec, replace = false) {
const query = "INSERT " + (replace ? "OR REPLACE" : "") + " INTO " + table_name + "(" + columns + ") VALUES (" + questionMarks + ")"; const query = "INSERT " + (replace ? "OR REPLACE" : "") + " INTO " + table_name + "(" + columns + ") VALUES (" + questionMarks + ")";
const res = await execute(db, query, Object.values(rec)); const res = await execute(query, Object.values(rec));
return res.lastID; return res.lastID;
} }
async function replace(db, table_name, rec) { async function replace(table_name, rec) {
return await insert(db, table_name, rec, true); return await insert(table_name, rec, true);
} }
async function beginTransaction(db) { async function beginTransaction() {
return await wrap(async () => db.run("BEGIN")); return await wrap(async db => db.run("BEGIN"));
} }
async function commit(db) { async function commit() {
return await wrap(async () => db.run("COMMIT")); return await wrap(async db => db.run("COMMIT"));
} }
async function rollback(db) { async function rollback() {
return await wrap(async () => db.run("ROLLBACK")); return await wrap(async db => db.run("ROLLBACK"));
} }
async function getSingleResult(query, params = []) { async function getSingleResult(query, params = []) {
const db = await dbReady; const db = await dbReady;
return await wrap(async () => db.get(query, ...params)); return await wrap(async db => db.get(query, ...params));
} }
async function getSingleResultOrNull(query, params = []) { async function getSingleResultOrNull(query, params = []) {
const db = await dbReady; const db = await dbReady;
const all = await wrap(async () => db.all(query, ...params)); const all = await wrap(async db => db.all(query, ...params));
return all.length > 0 ? all[0] : null; return all.length > 0 ? all[0] : null;
} }
@ -69,7 +69,7 @@ async function getSingleValue(query, params = []) {
async function getResults(query, params = []) { async function getResults(query, params = []) {
const db = await dbReady; const db = await dbReady;
return await wrap(async () => db.all(query, ...params)); return await wrap(async db => db.all(query, ...params));
} }
async function getMap(query, params = []) { async function getMap(query, params = []) {
@ -96,23 +96,24 @@ async function getFlattenedResults(key, query, params = []) {
return list; return list;
} }
async function execute(db, query, params = []) { async function execute(query, params = []) {
return await wrap(async () => db.run(query, ...params)); return await wrap(async db => db.run(query, ...params));
} }
async function executeScript(db, query) { async function executeScript(query) {
return await wrap(async () => db.exec(query)); return await wrap(async db => db.exec(query));
} }
async function remove(db, tableName, noteId) { async function remove(tableName, noteId) {
return await execute(db, "DELETE FROM " + tableName + " WHERE note_id = ?", [noteId]); return await execute("DELETE FROM " + tableName + " WHERE note_id = ?", [noteId]);
} }
async function wrap(func) { async function wrap(func) {
const thisError = new Error(); const thisError = new Error();
const db = await dbReady;
try { try {
return await func(); return await func(db);
} }
catch (e) { catch (e) {
log.error("Error executing query. Inner exception: " + e.stack + thisError.stack); log.error("Error executing query. Inner exception: " + e.stack + thisError.stack);
@ -121,25 +122,38 @@ async function wrap(func) {
} }
} }
async function doInTransaction(func) { let transactionPromise = null;
const error = new Error(); // to capture correct stack trace in case of exception
const db = await createConnection();
async function doInTransaction(func) {
while (transactionPromise !== null) {
await transactionPromise;
}
const error = new Error(); // to capture correct stack trace in case of exception
transactionPromise = new Promise(async (resolve, reject) => {
try { try {
await beginTransaction(db); await beginTransaction();
await func(db); await func();
await commit(db); await commit();
resolve();
transactionPromise = null;
} }
catch (e) { catch (e) {
log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack); log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack + error.stack);
await rollback(db); await rollback();
resolve();
transactionPromise = null;
throw e; throw e;
} }
});
} }
dbReady dbReady

View File

@ -149,8 +149,8 @@ async function pullSync(syncContext) {
throw new Error("Unrecognized entity type " + sync.entity_name); throw new Error("Unrecognized entity type " + sync.entity_name);
} }
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await options.setOption(db, 'last_synced_pull', sync.id); await options.setOption('last_synced_pull', sync.id);
}); });
} }
@ -184,8 +184,8 @@ async function pushSync(syncContext) {
lastSyncedPush = sync.id; lastSyncedPush = sync.id;
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await options.setOption(db, 'last_synced_push', lastSyncedPush); await options.setOption('last_synced_push', lastSyncedPush);
}); });
} }
} }

View File

@ -2,32 +2,32 @@ const sql = require('./sql');
const source_id = require('./source_id'); const source_id = require('./source_id');
const utils = require('./utils'); const utils = require('./utils');
async function addNoteSync(db, noteId, sourceId) { async function addNoteSync(noteId, sourceId) {
await addEntitySync(db, "notes", noteId, sourceId) await addEntitySync("notes", noteId, sourceId)
} }
async function addNoteTreeSync(db, noteTreeId, sourceId) { async function addNoteTreeSync(noteTreeId, sourceId) {
await addEntitySync(db, "notes_tree", noteTreeId, sourceId) await addEntitySync("notes_tree", noteTreeId, sourceId)
} }
async function addNoteReorderingSync(db, parentNoteTreeId, sourceId) { async function addNoteReorderingSync(parentNoteTreeId, sourceId) {
await addEntitySync(db, "notes_reordering", parentNoteTreeId, sourceId) await addEntitySync("notes_reordering", parentNoteTreeId, sourceId)
} }
async function addNoteHistorySync(db, noteHistoryId, sourceId) { async function addNoteHistorySync(noteHistoryId, sourceId) {
await addEntitySync(db, "notes_history", noteHistoryId, sourceId); await addEntitySync("notes_history", noteHistoryId, sourceId);
} }
async function addOptionsSync(db, optName, sourceId) { async function addOptionsSync(optName, sourceId) {
await addEntitySync(db, "options", optName, sourceId); await addEntitySync("options", optName, sourceId);
} }
async function addRecentNoteSync(db, notePath, sourceId) { async function addRecentNoteSync(notePath, sourceId) {
await addEntitySync(db, "recent_notes", notePath, sourceId); await addEntitySync("recent_notes", notePath, sourceId);
} }
async function addEntitySync(db, entityName, entityId, sourceId) { async function addEntitySync(entityName, entityId, sourceId) {
await sql.replace(db, "sync", { await sql.replace("sync", {
entity_name: entityName, entity_name: entityName,
entity_id: entityId, entity_id: entityId,
sync_date: utils.nowTimestamp(), sync_date: utils.nowTimestamp(),

View File

@ -10,43 +10,43 @@ async function updateNote(entity, links, sourceId) {
const origNote = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [entity.note_id]); const origNote = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [entity.note_id]);
if (!origNote || origNote.date_modified <= entity.date_modified) { if (!origNote || origNote.date_modified <= entity.date_modified) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.replace(db, "notes", entity); await sql.replace("notes", entity);
await sql.remove(db, "links", entity.note_id); await sql.remove("links", entity.note_id);
for (const link of links) { for (const link of links) {
delete link['lnk_id']; delete link['lnk_id'];
//await sql.insert(db, 'link', link); //await sql.insert('link', link);
} }
await sync_table.addNoteSync(db, entity.note_id, sourceId); await sync_table.addNoteSync(entity.note_id, sourceId);
await eventLog.addNoteEvent(db, entity.note_id, "Synced note <note>"); await eventLog.addNoteEvent(entity.note_id, "Synced note <note>");
}); });
log.info("Update/sync note " + entity.note_id); log.info("Update/sync note " + entity.note_id);
} }
else { else {
await eventLog.addNoteEvent(db, entity.note_id, "Sync conflict in note <note>, " + utils.formatTwoTimestamps(origNote.date_modified, entity.date_modified)); await eventLog.addNoteEvent(entity.note_id, "Sync conflict in note <note>, " + utils.formatTwoTimestamps(origNote.date_modified, entity.date_modified));
} }
} }
async function updateNoteTree(entity, sourceId) { async function updateNoteTree(entity, sourceId) {
const orig = await sql.getSingleResultOrNull("SELECT * FROM notes_tree WHERE note_tree_id = ?", [entity.note_tree_id]); const orig = await sql.getSingleResultOrNull("SELECT * FROM notes_tree WHERE note_tree_id = ?", [entity.note_tree_id]);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (orig === null || orig.date_modified < entity.date_modified) { if (orig === null || orig.date_modified < entity.date_modified) {
delete entity.is_expanded; delete entity.is_expanded;
await sql.replace(db, 'notes_tree', entity); await sql.replace('notes_tree', entity);
await sync_table.addNoteTreeSync(db, entity.note_tree_id, sourceId); await sync_table.addNoteTreeSync(entity.note_tree_id, sourceId);
log.info("Update/sync note tree " + entity.note_tree_id); log.info("Update/sync note tree " + entity.note_tree_id);
} }
else { else {
await eventLog.addNoteEvent(db, entity.note_tree_id, "Sync conflict in note tree <note>, " + utils.formatTwoTimestamps(orig.date_modified, entity.date_modified)); await eventLog.addNoteEvent(entity.note_tree_id, "Sync conflict in note tree <note>, " + utils.formatTwoTimestamps(orig.date_modified, entity.date_modified));
} }
}); });
} }
@ -54,27 +54,27 @@ async function updateNoteTree(entity, sourceId) {
async function updateNoteHistory(entity, sourceId) { async function updateNoteHistory(entity, sourceId) {
const orig = await sql.getSingleResultOrNull("SELECT * FROM notes_history WHERE note_history_id = ?", [entity.note_history_id]); const orig = await sql.getSingleResultOrNull("SELECT * FROM notes_history WHERE note_history_id = ?", [entity.note_history_id]);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (orig === null || orig.date_modified_to < entity.date_modified_to) { if (orig === null || orig.date_modified_to < entity.date_modified_to) {
await sql.replace(db, 'notes_history', entity); await sql.replace('notes_history', entity);
await sync_table.addNoteHistorySync(db, entity.note_history_id, sourceId); await sync_table.addNoteHistorySync(entity.note_history_id, sourceId);
log.info("Update/sync note history " + entity.note_history_id); log.info("Update/sync note history " + entity.note_history_id);
} }
else { else {
await eventLog.addNoteEvent(db, entity.note_id, "Sync conflict in note history for <note>, " + utils.formatTwoTimestamps(orig.date_modified_to, entity.date_modified_to)); await eventLog.addNoteEvent(entity.note_id, "Sync conflict in note history for <note>, " + utils.formatTwoTimestamps(orig.date_modified_to, entity.date_modified_to));
} }
}); });
} }
async function updateNoteReordering(entity, sourceId) { async function updateNoteReordering(entity, sourceId) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
Object.keys(entity.ordering).forEach(async key => { Object.keys(entity.ordering).forEach(async key => {
await sql.execute(db, "UPDATE notes_tree SET note_pos = ? WHERE note_tree_id = ?", [entity.ordering[key], key]); await sql.execute("UPDATE notes_tree SET note_pos = ? WHERE note_tree_id = ?", [entity.ordering[key], key]);
}); });
await sync_table.addNoteReorderingSync(db, entity.note_pid, sourceId); await sync_table.addNoteReorderingSync(entity.note_pid, sourceId);
}); });
} }
@ -85,16 +85,16 @@ async function updateOptions(entity, sourceId) {
const orig = await sql.getSingleResultOrNull("SELECT * FROM options WHERE opt_name = ?", [entity.opt_name]); const orig = await sql.getSingleResultOrNull("SELECT * FROM options WHERE opt_name = ?", [entity.opt_name]);
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
if (orig === null || orig.date_modified < entity.date_modified) { if (orig === null || orig.date_modified < entity.date_modified) {
await sql.replace(db, 'options', entity); await sql.replace('options', entity);
await sync_table.addOptionsSync(db, entity.opt_name, sourceId); await sync_table.addOptionsSync(entity.opt_name, sourceId);
await eventLog.addEvent(db, "Synced option " + entity.opt_name); await eventLog.addEvent("Synced option " + entity.opt_name);
} }
else { else {
await eventLog.addEvent(db, "Sync conflict in options for " + entity.opt_name + ", " + utils.formatTwoTimestamps(orig.date_modified, entity.date_modified)); await eventLog.addEvent("Sync conflict in options for " + entity.opt_name + ", " + utils.formatTwoTimestamps(orig.date_modified, entity.date_modified));
} }
}); });
} }
@ -103,10 +103,10 @@ async function updateRecentNotes(entity, sourceId) {
const orig = await sql.getSingleResultOrNull("SELECT * FROM recent_notes WHERE note_path = ?", [entity.note_path]); const orig = await sql.getSingleResultOrNull("SELECT * FROM recent_notes WHERE note_path = ?", [entity.note_path]);
if (orig === null || orig.date_accessed < entity.date_accessed) { if (orig === null || orig.date_accessed < entity.date_accessed) {
await sql.doInTransaction(async db => { await sql.doInTransaction(async () => {
await sql.replace(db, 'recent_notes', entity); await sql.replace('recent_notes', entity);
await sync_table.addRecentNoteSync(db, entity.note_path, sourceId); await sync_table.addRecentNoteSync(entity.note_path, sourceId);
}); });
} }
} }