diff --git a/cmd/data.js b/cmd/data.js index c65ee4930..0b979bb31 100644 --- a/cmd/data.js +++ b/cmd/data.js @@ -1,11 +1,14 @@ 'use strict' const parseLink = require('../lib/parse-link') -const { outputter, ansi } = require('./iface') +const { outputter, ansi, confirm, status } = require('./iface') const { ERR_INVALID_INPUT } = require('../errors') +const { PLATFORM_HYPERDB } = require('../constants') const padding = ' ' +const placeholder = '[ No results ]\n' const appsOutput = (bundles) => { + if (!bundles.length) return placeholder let out = '' for (const bundle of bundles) { out += `- ${ansi.bold(bundle.link)}\n` @@ -20,6 +23,7 @@ const appsOutput = (bundles) => { } const dhtOutput = (nodes) => { + if (!nodes.length) return placeholder let out = '' for (const node of nodes) { out += `${node.host}${ansi.dim(`:${node.port}`)}\n` @@ -28,6 +32,7 @@ const dhtOutput = (nodes) => { } const gcOutput = (records) => { + if (!records.length) return placeholder let out = '' for (const gc of records) { out += `- ${ansi.bold(gc.path)}\n` @@ -56,10 +61,10 @@ class Data { if (link) { const parsed = parseLink(link) if (!parsed) throw ERR_INVALID_INPUT(`Link "${link}" is not a valid key`) - const result = await this.ipc.data({ resource: 'link', secrets, link }) + const result = this.ipc.data({ resource: 'link', secrets, link }) await output(json, result, { tag: 'link' }, this.ipc) } else { - const result = await this.ipc.data({ resource: 'apps', secrets }) + const result = this.ipc.data({ resource: 'apps', secrets }) await output(json, result, { tag: 'apps' }, this.ipc) } } @@ -67,14 +72,43 @@ class Data { async dht (cmd) { const { command } = cmd const { json } = command.parent.flags - const result = await this.ipc.data({ resource: 'dht' }) + const result = this.ipc.data({ resource: 'dht' }) await output(json, result, { tag: 'dht' }, this.ipc) } async gc (cmd) { const { command } = cmd const { json } = command.parent.flags - const result = await this.ipc.data({ resource: 'gc' }) + const result = this.ipc.data({ resource: 'gc' }) await output(json, result, { tag: 'gc' }, this.ipc) } + + async reset (cmd) { + const { command } = cmd + const { yes } = command.flags + + if (!yes) { + const dialog = `${ansi.warning} Clearing database ${ansi.bold(PLATFORM_HYPERDB)}\n\n` + const ask = 'Type DELETE to confirm' + const delim = '?' + const validation = (val) => val === 'DELETE' + const msg = '\n' + ansi.cross + ' uppercase DELETE to confirm\n' + await confirm(dialog, ask, delim, validation, msg) + } + + const complete = await pick(this.ipc.dataReset(), (tag) => tag === 'complete') + complete ? status('Success\n', true) : status('Failure (ipc.dataReset)\n', false) + } +} + +function pick (stream, predicate) { + return new Promise((resolve, reject) => { + stream.on('error', reject) + const listener = ({ tag, data }) => { + if (!predicate(tag)) return + resolve(data) + stream.off('data', listener) + } + stream.on('data', listener) + }) } diff --git a/cmd/index.js b/cmd/index.js index 1f1372027..caa7e136e 100644 --- a/cmd/index.js +++ b/cmd/index.js @@ -194,6 +194,9 @@ module.exports = async (ipc, argv = Bare.argv.slice(1)) => { command('apps', summary('Installed apps'), arg('[link]', 'Filter by Pear link'), (cmd) => runners.data(ipc).apps(cmd)), command('dht', summary('DHT known-nodes cache'), (cmd) => runners.data(ipc).dht(cmd)), command('gc', summary('Garbage collection records'), (cmd) => runners.data(ipc).gc(cmd)), + command('reset', summary('Advanced. Clear local database'), + flag('--yes', 'Skip confirmation prompt'), (cmd) => runners.data(ipc).reset(cmd) + ), flag('--secrets', 'Show sensitive information, i.e. encryption-keys'), flag('--json', 'Newline delimited JSON output'), () => { console.log(data.help()) } diff --git a/package-lock.json b/package-lock.json index 90595851e..4d483a2a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,7 @@ "paparam": "^1.6.0", "pear-changelog": "^1.0.1", "pear-interface": "^1.0.0", - "pear-ipc": "^3.1.0", + "pear-ipc": "^3.3.0", "pear-link": "^2.1.1", "pear-updater": "^3.4.3", "pear-updater-bootstrap": "^1.2.0", @@ -4107,9 +4107,9 @@ } }, "node_modules/pear-ipc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pear-ipc/-/pear-ipc-3.2.0.tgz", - "integrity": "sha512-ccuI0xYV7UA1TiBV5uhGCy06uS71uaVBTeLY+yF987DHEuc9ggLe9/AM9fuWRdWeI/NxjJQFQXedm+wEYUd7Zg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pear-ipc/-/pear-ipc-3.3.0.tgz", + "integrity": "sha512-L2ZQ6r1UM+mVa0zxC/7B5B+K1bbvHms1makvS4gcNiwx9E1fXKWSVkvLZ0cfGf86+IH5YfzjJ63SbjTy77gABw==", "license": "Apache-2.0", "dependencies": { "bare-fs": "^4.0.1", diff --git a/package.json b/package.json index 181c7250f..7573d2515 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "paparam": "^1.6.0", "pear-changelog": "^1.0.1", "pear-interface": "^1.0.0", - "pear-ipc": "^3.1.0", + "pear-ipc": "^3.3.0", "pear-link": "^2.1.1", "pear-updater": "^3.4.3", "pear-updater-bootstrap": "^1.2.0", diff --git a/subsystems/sidecar/index.js b/subsystems/sidecar/index.js index df0d78725..74b97aa58 100644 --- a/subsystems/sidecar/index.js +++ b/subsystems/sidecar/index.js @@ -49,7 +49,8 @@ const ops = { Shift: require('./ops/shift'), Reset: require('./ops/reset'), Touch: require('./ops/touch'), - Data: require('./ops/data') + Data: require('./ops/data'), + DataReset: require('./ops/data/reset') } // ensure that we are registered as a link handler @@ -368,6 +369,8 @@ class Sidecar extends ReadyResource { data (params, client) { return new ops.Data(params, client, this) } + dataReset (params, client) { return new ops.DataReset(params, client, this) } + shift (params, client) { return new ops.Shift(params, client, this) } reset (params, client) { return new ops.Reset(params, client, this) } diff --git a/subsystems/sidecar/lib/model.js b/subsystems/sidecar/lib/model.js index 363509f04..7cbcdf1c2 100644 --- a/subsystems/sidecar/lib/model.js +++ b/subsystems/sidecar/lib/model.js @@ -1,4 +1,5 @@ 'use strict' +const fs = require('bare-fs') const HyperDB = require('hyperdb') const DBLock = require('db-lock') const dbSpec = require('../../../spec/db') @@ -6,6 +7,11 @@ const { PLATFORM_HYPERDB } = require('../../../constants') module.exports = class Model { constructor () { + this.init() + } + + init () { + LOG.trace('db', `ROCKS ('${PLATFORM_HYPERDB}')`) this.db = HyperDB.rocks(PLATFORM_HYPERDB, dbSpec) this.lock = new DBLock({ @@ -145,6 +151,13 @@ module.exports = class Model { } async close () { + LOG.trace('db', 'CLOSE') await this.db.close() } + + async reset () { + await this.close() + await fs.promises.rm(PLATFORM_HYPERDB, { recursive: true, force: true }) + this.init() + } } diff --git a/subsystems/sidecar/ops/data.js b/subsystems/sidecar/ops/data/index.js similarity index 95% rename from subsystems/sidecar/ops/data.js rename to subsystems/sidecar/ops/data/index.js index 4c7927ff5..069a8cf2e 100644 --- a/subsystems/sidecar/ops/data.js +++ b/subsystems/sidecar/ops/data/index.js @@ -1,6 +1,6 @@ 'use strict' const { pathToFileURL } = require('url-file-url') -const Opstream = require('../lib/opstream') +const Opstream = require('../../lib/opstream') module.exports = class Data extends Opstream { constructor (...args) { diff --git a/subsystems/sidecar/ops/data/reset.js b/subsystems/sidecar/ops/data/reset.js new file mode 100644 index 000000000..665d82a04 --- /dev/null +++ b/subsystems/sidecar/ops/data/reset.js @@ -0,0 +1,13 @@ +'use strict' +const Opstream = require('../../lib/opstream') + +module.exports = class DataReset extends Opstream { + constructor (...args) { + super((...args) => this.#op(...args), ...args) + } + + async #op () { + await this.sidecar.model.reset() + this.push({ tag: 'complete', data: true }) + } +}