import { setTimeout } from 'timers/promises' import { Low, JSONFile, Memory } from 'lowdb' import fs from 'fs' import { defaults, isObject } from './defaults.mjs' export default function GetDB(config, log, orgFilename = 'db.json') { let adapter = new Memory() let fullpath = 'in-memory' if (orgFilename) { fullpath = orgFilename adapter = new JSONFile(fullpath) } const db = new Low(adapter) db.id = 'id' db.filename = fullpath db.config = config db.createId = function(collection) { if (collection.length) { return (collection[collection.length - 1].id || 0) + 1 } return 1 } db.get = function(collection, id, returnIndex = false) { let col = db.getCollection(collection) for (let i = col.length - 1; i >= 0; i--) { if (col[i][db.id] === id) { if (returnIndex) return i return col[i] } } return null } db.getCollection = function(collection) { if (typeof(collection) === 'string') { return db.data[collection] } return collection } db.upsert = function(collection, item) { let col = db.getCollection(collection) if (item[db.id]) { let i = db.get(col, item[db.id], true) if (i !== null) { col[i] = item return } } else { item[db.id] = db.createId(col) } col.push(item) } db.upsertFirst = function(collection, item) { let col = db.getCollection(collection) if (item[db.id]) { let i = db.get(col, item[db.id], true) if (i !== null) { col[i] = item return } } else { item[db.id] = db.createId(col) } col.splice(0, 0, item) } db.remove = function(collection, itemOrId) { let col = db.getCollection(collection) let id = itemOrId if (typeof(id) === 'object') { id = id[db.id] } for (let i = col.length - 1; i >= 0; i--) { if (col[i][db.id] === id) { col.splice(i, 1) return true } } return false } db.addApplication = function(name) { db.data.core[name] ||= {} defaults(db.data.core[name], { active: '', latestInstalled: '', updater: '', versions: [], }) } db.log = log db._write = db.write.bind(db) db.write = function() { return this._write() // Do couple of retries. Sometimes it fails randomly doing atomic writes. .catch(() => { return setTimeout(20).then(() => { return this._write() }) }) .catch(() => { return setTimeout(50).then(() => { return this._write() }) }) .catch(() => { return setTimeout(100).then(() => { return this._write() }) }) .catch((err) => { try { db.writeSync() } catch { this.log.error(err, 'Error saving to db') } }) } db.writeSync = function() { try { fs.writeFileSync(db.filename, JSON.stringify(db.data)) } catch(err) { db.log.error(err, `Error during writeSync to ${db.filename}`) } } return db.read() .then(function() { if (!isObject(db.data)) { if (fullpath !== 'in-memory') { db.log.warn(`File ${fullpath} was empty or not a json object, clearing it.`) } db.data = {} } defaults(db.data, { core: { version: 1 } }) return db.write() }) .then( function() { return db }, function(err) { let wrapped = new Error(`Error writing to ${fullpath}: ${err.message} (${err.code})`) wrapped.code = err.code throw wrapped } ) }