Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PhStore: unified persistant storage in browser and tauri #1250

Merged
merged 13 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
*/

require("./NodeEventDispatcher");
const lmdb = require("./lmdb");
const readline = require('readline');
const http = require('http');
const os = require('os');
Expand Down Expand Up @@ -160,7 +161,13 @@ function processCommand(line) {
try{
let jsonCmd = JSON.parse(line);
switch (jsonCmd.commandCode) {
case "terminate": process.exit(0); return;
case "terminate":
lmdb.dumpDBToFileAndCloseDB()
.catch(console.error)
.finally(()=>{
process.exit(0);
});
return;
case "heartBeat":
resetOrphanExitTimer();
return;
Expand Down
139 changes: 139 additions & 0 deletions src-node/lmdb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const lmdb= require("lmdb");
const path= require("path");
const fs= require("fs");
const NodeConnector = require("./node-connector");

const STORAGE_NODE_CONNECTOR = "ph_storage";
const EXTERNAL_CHANGE_POLL_INTERVAL = 800;
const EVENT_CHANGED = "change";
const nodeConnector = NodeConnector.createNodeConnector(STORAGE_NODE_CONNECTOR, exports);
const watchExternalKeys = {};

let storageDB,
dumpFileLocation;
async function openDB(lmdbDir) {
// see LMDB api docs in https://www.npmjs.com/package/lmdb?activeTab=readme
lmdbDir = path.join(lmdbDir, "storageDB");
storageDB = lmdb.open({
path: lmdbDir,
compression: false
});
console.log("storageDB location is :", lmdbDir);
dumpFileLocation = path.join(lmdbDir, "storageDBDump.json");
}

function flushDB() {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
return storageDB.flushed; // wait for disk write complete
}


async function dumpDBToFile() {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
await storageDB.flushed; // wait for disk write complete
await storageDB.transaction(() => {
const storageMap = {};
for(const key of storageDB.getKeys()){
storageMap[key] = storageDB.get(key);
}
// this is a critical session, so its guarenteed that only one file write operation will be done
// if there are multiple instances trying to dump the file. Multi process safe.
fs.writeFileSync(dumpFileLocation, JSON.stringify(storageMap));
});
return dumpFileLocation;
}

/**
* Takes the current state of the storage database, writes it to a file in JSON format,
* and then closes the database. This is multi-process safe, ie, if multiple processes tries to write the
* dump file at the same time, only one process will be allowed at a time while the others wait in a critical session.
*
* @returns {Promise<string>} - A promise that resolves to the path of the dumped file.
*/
async function dumpDBToFileAndCloseDB() {
await dumpDBToFile();
await storageDB.close();
return dumpFileLocation;
}

/**
* Puts an item with the specified key and value into the storage database.
*
* @param {string} key - The key of the item.
* @param {*} value - The value of the item.
* @returns {Promise} - A promise that resolves when the put is persisted to disc.
*/
function putItem({key, value}) {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
if(watchExternalKeys[key] && typeof value === 'object' && value.t) {
watchExternalKeys[key] = value.t;
}
return storageDB.put(key, value);
}

/**
* Retrieve an item from the storage database.
*
* @param {string} key - The key of the item to retrieve.
* @returns {Promise<*>} A promise that resolves with the retrieved item.
*/
async function getItem(key) {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
return storageDB.get(key);
}

async function watchExternalChanges({key, t}) {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
watchExternalKeys[key] = t;
}

async function unwatchExternalChanges(key) {
if(!storageDB){
throw new Error("LMDB Storage operation called before openDB call");
}
delete watchExternalKeys[key];
}

function updateExternalChangesFromLMDB() {
const watchedKeys = Object.keys(watchExternalKeys);
if(!watchedKeys.length) {
return;
}
const changedKV = {};
let changesPresent = false
for(let key of watchedKeys) {
const t = watchExternalKeys[key];
const newVal = storageDB.get(key);
if(newVal && (newVal.t > t)){
// this is newer
watchExternalKeys[key]= newVal.t;
changedKV[key] = newVal;
changesPresent = true;
}
}
if(changesPresent) {
nodeConnector.triggerPeer(EVENT_CHANGED, changedKV);
}
}

setInterval(updateExternalChangesFromLMDB, EXTERNAL_CHANGE_POLL_INTERVAL);

exports.openDB = openDB;
exports.dumpDBToFile = dumpDBToFile;
exports.dumpDBToFileAndCloseDB = dumpDBToFileAndCloseDB;
exports.putItem = putItem;
exports.getItem = getItem;
exports.flushDB = flushDB;
exports.watchExternalChanges = watchExternalChanges;
exports.unwatchExternalChanges = unwatchExternalChanges;

Loading
Loading