Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Commit

Permalink
feat: Stat command working on directories
Browse files Browse the repository at this point in the history
  • Loading branch information
achingbrain committed Apr 13, 2018
1 parent bad24b3 commit 4671b2e
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

module.exports = {
ls: require('./ls'),
mkdir: require('./mkdir')
mkdir: require('./mkdir'),
stat: require('./stat')
}
89 changes: 89 additions & 0 deletions src/core/stat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict'

const exporter = require('ipfs-unixfs-engine').exporter
const unmarshal = require('ipfs-unixfs').unmarshal
const promisify = require('promisify-es6')
const pull = require('pull-stream')
const bs58 = require('bs58')
const CID = require('cids')
const {
collect
} = pull
const {
withMfsRoot,
validatePath
} = require('./utils')
const {
waterfall
} = require('async')

const defaultOptions = {
hash: false,
size: false,
withLocal: false
}

module.exports = function mfsStat (ipfs) {
return promisify((path, options, callback) => {
withMfsRoot(ipfs, (error, root) => {
if (error) {
return callback(error)
}

if (typeof options === 'function') {
callback = options
options = {}
}

options = Object.assign({}, defaultOptions, options)

try {
path = validatePath(path)
root = root.toBaseEncodedString()
} catch (error) {
return callback(error)
}

waterfall([
(done) => pull(
exporter(`/ipfs/${root}${path}`, ipfs._ipld),
collect(done)
),
(results, done) => {
if (!results.length) {
return done(new Error('file does not exist'))
}

done(null, results[0])
},
(result, done) => {
if (options.hash) {
return done(null, {
hash: bs58.encode(result.hash)
})
} else if (options.size) {
return done(null, {
size: result.size
})
}

waterfall([
(next) => ipfs.dag.get(new CID(result.hash), next),
(result, next) => next(null, result.value),
(node, next) => {
const data = unmarshal(node.data)

next(null, {
hash: node.multihash,
size: data.blockSizes.reduce((acc, curr) => acc + curr, 0),
cumulativeSize: node.size,
childBlocks: node.links.length,
type: data.type
})
}
], done)
}
], callback)
})
})
}
7 changes: 5 additions & 2 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
} = require('async')
const {
ls,
mkdir
mkdir,
stat
} = require('../../src/core')

const createMfs = promisify((cb) => {
Expand All @@ -27,12 +28,14 @@ const createMfs = promisify((cb) => {
done(null, {
ls: ls(node),
mkdir: mkdir(node),
stat: stat(node),
node: node
})
}
], cb)
})

module.exports = {
createMfs
createMfs,
EMPTY_DIRECTORY_HASH: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'
}
99 changes: 99 additions & 0 deletions test/stat.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect

const {
createMfs,
EMPTY_DIRECTORY_HASH
} = require('./fixtures')

describe('stat', function () {
this.timeout(30000)

let mfs

before(() => {
return createMfs()
.then(instance => {
mfs = instance
})
})

after((done) => {
mfs.node.stop(done)
})

it('refuses to stat files with an empty path', () => {
return mfs.stat('')
.then(() => expect.fail('No error was thrown for an empty path'))
.catch(error => {
expect(error.message).to.contain('paths must not be empty')
})
})

it('refuses to lists files with an invalid path', () => {
return mfs.stat('not-valid')
.then(() => expect.fail('No error was thrown for an empty path'))
.catch(error => {
expect(error.message).to.contain('paths must start with a leading /')
})
})

it('fails to stat non-existent file', () => {
return mfs.stat('/i-do-not-exist')
.then(() => expect.fail('No error was thrown for a non-existent file'))
.catch(error => {
expect(error.message).to.contain('file does not exist')
})
})

it('stats an empty directory', () => {
const path = '/empty-directory'

return mfs.mkdir('/empty-directory')
.then(() => mfs.stat(path))
.then(stats => {
expect(stats.size).to.equal(0)
expect(stats.cumulativeSize).to.equal(4)
expect(stats.childBlocks).to.equal(0)
expect(stats.type).to.equal('directory')
})
})

it('returns only a hash', () => {
const path = '/empty-directory'

return mfs.mkdir('/empty-directory')
.then(() => mfs.stat(path, {
hash: true
}))
.then(stats => {
expect(Object.keys(stats).length).to.equal(1)
expect(stats.hash).to.equal(EMPTY_DIRECTORY_HASH)
})
})

it('returns only the size', () => {
const path = '/empty-directory'

return mfs.mkdir('/empty-directory')
.then(() => mfs.stat(path, {
size: true
}))
.then(stats => {
expect(Object.keys(stats).length).to.equal(1)
expect(stats.size).to.equal(4) // protobuf size?!
})
})

it.skip('computes how much of the DAG is local', () => {

})

it.skip('stats a file', () => {

})
})

0 comments on commit 4671b2e

Please sign in to comment.