actual-server/sync-simple.js
2022-12-08 17:38:26 -05:00

99 lines
2.3 KiB
JavaScript

let { existsSync, readFileSync } = require('fs');
let { join } = require('path');
let { openDatabase } = require('./db');
let { getPathForGroupFile } = require('./util/paths');
let actual = require('@actual-app/api');
let merkle = actual.internal.merkle;
let SyncPb = actual.internal.SyncProtoBuf;
let Timestamp = actual.internal.timestamp.Timestamp;
function getGroupDb(groupId) {
let path = getPathForGroupFile(groupId);
let needsInit = !existsSync(path);
let db = openDatabase(path);
if (needsInit) {
let sql = readFileSync(join(__dirname, 'sql/messages.sql'), 'utf8');
db.exec(sql);
}
return db;
}
function addMessages(db, messages) {
let returnValue;
db.transaction(() => {
let trie = getMerkle(db);
if (messages.length > 0) {
for (let msg of messages) {
let info = db.mutate(
`INSERT OR IGNORE INTO messages_binary (timestamp, is_encrypted, content)
VALUES (?, ?, ?)`,
[
msg.getTimestamp(),
msg.getIsencrypted() ? 1 : 0,
Buffer.from(msg.getContent())
]
);
if (info.changes > 0) {
trie = merkle.insert(trie, Timestamp.parse(msg.getTimestamp()));
}
}
}
trie = merkle.prune(trie);
db.mutate(
'INSERT INTO messages_merkles (id, merkle) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET merkle = ?',
[JSON.stringify(trie), JSON.stringify(trie)]
);
returnValue = trie;
});
return returnValue;
}
function getMerkle(db) {
let rows = db.all('SELECT * FROM messages_merkles');
if (rows.length > 0) {
return JSON.parse(rows[0].merkle);
} else {
// No merkle trie exists yet (first sync of the app), so create a
// default one.
return {};
}
}
function sync(messages, since, groupId) {
let db = getGroupDb(groupId);
let newMessages = db.all(
`SELECT * FROM messages_binary
WHERE timestamp > ?
ORDER BY timestamp`,
[since]
);
let trie = addMessages(db, messages);
db.close();
return {
trie,
newMessages: newMessages.map((msg) => {
const envelopePb = new SyncPb.MessageEnvelope();
envelopePb.setTimestamp(msg.timestamp);
envelopePb.setIsencrypted(msg.is_encrypted);
envelopePb.setContent(msg.content);
return envelopePb;
})
};
}
module.exports = { sync };