diff --git a/README.md b/README.md index a0877a6e..e49bca20 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,20 @@ Sets the API address. * `value` should be a [Multiaddr](https://github.com/multiformats/js-multiaddr) or a String representing a valid one. +### `repo.stat ([options], callback)` + +Gets the repo status. + +`options` is an object which might contain the key `human`, which is a boolean indicating whether or not the `repoSize` should be displayed in MiB or not. + +`callback` is a function with the signature `function (err, stats)`, where `stats` is an Object with the following keys: + +- `numObjects` +- `repoPath` +- `repoSize` +- `version` +- `storageMax` + ## Notes - [Explanation of how repo is structured](https://github.com/ipfs/js-ipfs-repo/pull/111#issuecomment-279948247) diff --git a/package.json b/package.json index 2e7e8a40..9a97d23c 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,13 @@ "dependencies": { "async": "^2.6.0", "base32.js": "~0.1.0", + "big.js": "^5.0.3", "cids": "~0.5.2", - "interface-datastore": "~0.4.2", "datastore-core": "~0.4.0", "datastore-fs": "~0.4.2", "datastore-level": "~0.7.0", "debug": "^3.1.0", + "interface-datastore": "~0.4.2", "ipfs-block": "~0.6.1", "level-js": "timkuijsten/level.js#idbunwrapper", "leveldown": "^1.7.2", @@ -69,7 +70,8 @@ "lodash.get": "^4.4.2", "lodash.has": "^4.5.2", "lodash.set": "^4.3.2", - "multiaddr": "^3.0.1" + "multiaddr": "^3.0.1", + "pull-stream": "^3.6.1" }, "license": "MIT", "contributors": [ diff --git a/src/blockstore.js b/src/blockstore.js index a07644dd..46a04b91 100644 --- a/src/blockstore.js +++ b/src/blockstore.js @@ -8,6 +8,7 @@ const Block = require('ipfs-block') const setImmediate = require('async/setImmediate') const reject = require('async/reject') const CID = require('cids') +const pull = require('pull-stream') /** * Transform a raw buffer to a base32 encoded key. @@ -49,6 +50,19 @@ function maybeWithSharding (filestore, options, callback) { function createBaseStore (store) { return { + /** + * Query the store. + * + * @param {object} query + * @param {function(Error, Array)} callback + * @return {void} + */ + query (query, callback) { + pull( + store.query(query), + pull.collect(callback) + ) + }, /** * Get a single block by CID. * diff --git a/src/index.js b/src/index.js index 8c5bcfcc..a5883c40 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,8 @@ const each = require('async/each') const assert = require('assert') const path = require('path') const debug = require('debug') +const Big = require('big.js') +const pull = require('pull-stream') const backends = require('./backends') const version = require('./version') @@ -17,6 +19,8 @@ const defaultOptions = require('./default-options') const log = debug('repo') +const noLimit = Number.MAX_SAFE_INTEGER + const lockers = { memory: require('./lock-memory'), fs: require('./lock') @@ -200,6 +204,81 @@ class IpfsRepo { exists (callback) { this.version.exists(callback) } + + /** + * Get repo status. + * + * @param {Object} options + * @param {Boolean} options.human + * @param {function(Error, Object)} callback + * @return {void} + */ + stat (options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + options = Object.assign({}, {human: false}, options) + + parallel({ + storageMax: (cb) => this.config.get('Datastore.StorageMax', (err, max) => { + if (err) { + cb(null, new Big(noLimit)) + } else { + cb(null, new Big(max)) + } + }), + version: (cb) => this.version.get(cb), + blocks: (cb) => this.blocks.query({}, (err, list) => { + list = list || [] + + const count = new Big(list.length) + let size = new Big(0) + + list.forEach(block => { + size = size + .plus(block.value.byteLength) + .plus(block.key._buf.byteLength) + }) + + cb(err, { + count: count, + size: size + }) + }), + datastore: (cb) => getSize(this.datastore, cb), + keys: (cb) => getSize(this.keys, cb) + }, (err, results) => { + if (err) return callback(err) + + let size = results.blocks.size + .plus(results.datastore) + .plus(results.keys) + + if (options.human) { + size = size.div(1048576) + } + + callback(null, { + repoPath: this.path, + storageMax: results.storageMax, + version: results.version, + numObjects: results.blocks.count, + repoSize: size + }) + }) + } +} + +function getSize (queryFn, callback) { + pull( + queryFn.query({}), + pull.reduce((sum, block) => { + return sum + .plus(block.value.byteLength) + .plus(block.key._buf.byteLength) + }, new Big(0), callback)) } module.exports = IpfsRepo diff --git a/test/node.js b/test/node.js index 1deb6ddb..5bb9c323 100644 --- a/test/node.js +++ b/test/node.js @@ -61,6 +61,7 @@ describe('IPFS Repo Tests onNode.js', () => { require('./blockstore-test')(repo) require('./datastore-test')(repo) require('./keystore-test')(repo) + require('./stat-test')(repo) if (!r.init) { require('./interop-test')(repo) } diff --git a/test/stat-test.js b/test/stat-test.js new file mode 100644 index 00000000..c8ae84fb --- /dev/null +++ b/test/stat-test.js @@ -0,0 +1,46 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect + +module.exports = (repo) => { + describe('stat', () => { + it('get stats', (done) => { + repo.stat((err, stats) => { + expect(err).to.not.exist() + expect(stats).to.exist() + expect(stats).to.have.property('numObjects') + expect(stats).to.have.property('version') + expect(stats).to.have.property('repoPath') + expect(stats).to.have.property('repoSize') + expect(stats).to.have.property('storageMax') + + expect(stats.numObjects > '0').to.eql(true) + expect(stats.version > '0').to.eql(true) + expect(stats.repoSize > '0').to.eql(true) + expect(stats.storageMax > '0').to.eql(true) + done() + }) + }) + + it('get human stats', (done) => { + repo.stat({human: true}, (err, stats) => { + expect(err).to.not.exist() + expect(stats).to.exist() + expect(stats).to.have.property('numObjects') + expect(stats).to.have.property('version') + expect(stats).to.have.property('repoPath') + expect(stats).to.have.property('repoSize') + expect(stats).to.have.property('storageMax') + + expect(stats.numObjects > '0').to.eql(true) + expect(stats.version > '0').to.eql(true) + expect(stats.repoSize > '0').to.eql(true) + expect(stats.storageMax > '0').to.eql(true) + done() + }) + }) + }) +}