@@ -5,15 +5,31 @@ module.exports = {

describe: 'Fetch and cat an IPFS path referencing a file',

builder: {},
builder: {
offset: {
alias: 'o',
type: 'integer',
describe: 'Byte offset to begin reading from'
},
length: {
alias: ['n', 'count'],
type: 'integer',
describe: 'Maximum number of bytes to read'
}
},

handler (argv) {
let path = argv['ipfsPath']
if (path.indexOf('/ipfs/') !== 1) {
path = path.replace('/ipfs/', '')
}

const stream = argv.ipfs.files.catReadableStream(path)
const options = {
offset: argv.offset,
length: argv.length
}

const stream = argv.ipfs.files.catReadableStream(path, options)

stream.once('error', (err) => {
throw err
@@ -145,7 +145,7 @@ module.exports = function files (self) {
)
}

function _catPullStream (ipfsPath) {
function _catPullStream (ipfsPath, options) {
if (typeof ipfsPath === 'function') {
throw new Error('You must supply an ipfsPath')
}
@@ -158,7 +158,7 @@ module.exports = function files (self) {
const d = deferred.source()

pull(
exporter(ipfsPath, self._ipld),
exporter(ipfsPath, self._ipld, options),
pull.collect((err, files) => {
if (err) { return d.abort(err) }
if (files && files.length > 1) {
@@ -185,9 +185,12 @@ module.exports = function files (self) {
const recursive = options && options.recursive
const pathDepth = path.split('/').length
const maxDepth = recursive ? global.Infinity : pathDepth
const opts = Object.assign({}, {
maxDepth: maxDepth
}, options)

return pull(
exporter(ipfsPath, self._ipld, { maxDepth: maxDepth }),
exporter(ipfsPath, self._ipld, opts),
pull.filter(node =>
recursive ? node.depth >= pathDepth : node.depth === pathDepth
),
@@ -270,23 +273,41 @@ module.exports = function files (self) {

addPullStream: _addPullStream,

cat: promisify((ipfsPath, callback) => {
cat: promisify((ipfsPath, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}

if (typeof callback !== 'function') {
throw new Error('Please supply a callback to ipfs.files.cat')
}

pull(
_catPullStream(ipfsPath),
_catPullStream(ipfsPath, options),
pull.collect((err, buffers) => {
if (err) { return callback(err) }
callback(null, Buffer.concat(buffers))
})
)
}),

catReadableStream: (ipfsPath) => toStream.source(_catPullStream(ipfsPath)),
catReadableStream: (ipfsPath, options) => toStream.source(_catPullStream(ipfsPath, options)),

catPullStream: (ipfsPath, options) => _catPullStream(ipfsPath, options),

get: promisify((ipfsPath, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}

catPullStream: _catPullStream,
if (typeof callback !== 'function') {
throw new Error('Please supply a callback to ipfs.files.get')
}

get: promisify((ipfsPath, callback) => {
pull(
exporter(ipfsPath, self._ipld),
exporter(ipfsPath, self._ipld, options),
pull.asyncMap((file, cb) => {
if (file.content) {
pull(
@@ -305,10 +326,10 @@ module.exports = function files (self) {
)
}),

getReadableStream: (ipfsPath) => {
getReadableStream: (ipfsPath, options) => {
return toStream.source(
pull(
exporter(ipfsPath, self._ipld),
exporter(ipfsPath, self._ipld, options),
pull.map((file) => {
if (file.content) {
file.content = toStream.source(file.content)
@@ -321,8 +342,8 @@ module.exports = function files (self) {
)
},

getPullStream: (ipfsPath) => {
return exporter(ipfsPath, self._ipld)
getPullStream: (ipfsPath, options) => {
return exporter(ipfsPath, self._ipld, options)
},

lsImmutable: promisify((ipfsPath, options, callback) => {
@@ -17,6 +17,18 @@ const ndjson = require('pull-ndjson')

exports = module.exports

function numberFromQuery (query, key) {
if (query && query[key] !== undefined) {
const value = parseInt(query[key], 10)

if (isNaN(value)) {
return undefined
}

return value
}
}

// common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args`
exports.parseKey = (request, reply) => {
if (!request.query.arg) {
@@ -47,7 +59,11 @@ exports.parseKey = (request, reply) => {
}

reply({
key: request.query.arg
key: request.query.arg,
options: {
offset: numberFromQuery(request.query, 'offset'),
length: numberFromQuery(request.query, 'length')
}
})
}

@@ -58,9 +74,10 @@ exports.cat = {
// main route handler which is called after the above `parseArgs`, but only if the args were valid
handler: (request, reply) => {
const key = request.pre.args.key
const options = request.pre.args.options
const ipfs = request.server.app.ipfs

ipfs.files.cat(key, (err, stream) => {
ipfs.files.cat(key, options, (err, stream) => {
if (err) {
log.error(err)
if (err.message === 'No such file') {
@@ -345,6 +345,24 @@ describe('files', () => runOnAndOff((thing) => {
})
})

it('cat part of a file using `count`', function () {
this.timeout(30 * 1000)

return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --count 5')
.then((out) => {
expect(out).to.eql(readme.substring(21, 26))
})
})

it('cat part of a file using `length`', function () {
this.timeout(30 * 1000)

return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --length 5')
.then((out) => {
expect(out).to.eql(readme.substring(21, 26))
})
})

it('cat non-existent file', () => {
return ipfs('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB/dummy')
.then(() => expect.fail(0, 1, 'Should have thrown an error'))