diff --git a/src/api/cat.js b/src/api/cat.js index 8b61f66a1..06e9bac90 100644 --- a/src/api/cat.js +++ b/src/api/cat.js @@ -1,8 +1,7 @@ 'use strict' -const bs58 = require('bs58') -const isIPFS = require('is-ipfs') const promisify = require('promisify-es6') +const cleanMultihash = require('../clean-multihash') module.exports = (send) => { const cat = promisify((multihash, callback) => { @@ -15,13 +14,3 @@ module.exports = (send) => { }) return cat } - -function cleanMultihash (multihash) { - if (!isIPFS.multihash(multihash)) { - throw new Error('not valid multihash') - } - if (Buffer.isBuffer(multihash)) { - return bs58.encode(multihash) - } - return multihash -} diff --git a/src/api/get.js b/src/api/get.js new file mode 100644 index 000000000..b1b833d9d --- /dev/null +++ b/src/api/get.js @@ -0,0 +1,30 @@ +'use strict' + +const tarStreamToObjects = require('../tar-stream-to-objects') +const cleanMultihash = require('../clean-multihash') +const promisify = require('promisify-es6') + +module.exports = (send) => { + return promisify(function get (path, opts, cb) { + if (typeof opts === 'function' && !cb) { + cb = opts + opts = {} + } + + // opts is the real callback -- 'cb' is being injected by promisify + if (typeof opts === 'function' && typeof cb === 'function') { + cb = opts + opts = {} + } + + try { + path = cleanMultihash(path) + } catch (err) { + return cb(err) + } + + var sendWithTransform = send.withTransform(tarStreamToObjects) + + return sendWithTransform('get', path, opts, null, cb) + }) +} diff --git a/src/clean-multihash.js b/src/clean-multihash.js new file mode 100644 index 000000000..bbf3f9a39 --- /dev/null +++ b/src/clean-multihash.js @@ -0,0 +1,15 @@ +'use strict' + +const bs58 = require('bs58') +const isIPFS = require('is-ipfs') + +module.exports = function (multihash) { + if (!isIPFS.multihash(multihash)) { + throw new Error('not valid multihash') + } + if (Buffer.isBuffer(multihash)) { + return bs58.encode(multihash) + } + return multihash +} + diff --git a/src/load-commands.js b/src/load-commands.js index 74bd33540..e9b6072cd 100644 --- a/src/load-commands.js +++ b/src/load-commands.js @@ -13,6 +13,7 @@ function requireCommands () { dht: require('./api/dht'), diag: require('./api/diag'), id: require('./api/id'), + get: require('./api/get'), log: require('./api/log'), ls: require('./api/ls'), mount: require('./api/mount'), @@ -32,6 +33,12 @@ function requireCommands () { const files = require('./api/files')(send) files.add = require('./api/add')(send) files.createAddStream = require('./api/add-stream.js')(send) + files.get = require('./api/get')(send) + + // aliases + cmds.add = files.add + cmds.createAddStream = files.createAddStream + cmds.get = files.get return files } diff --git a/src/tar-stream-to-objects.js b/src/tar-stream-to-objects.js new file mode 100644 index 000000000..888aca797 --- /dev/null +++ b/src/tar-stream-to-objects.js @@ -0,0 +1,32 @@ +'use strict' + +const tar = require('tar-stream') +const Readable = require('readable-stream') +const through = require('through2') + +// transform tar stream into readable stream of +// { path: 'string', content: Readable } +module.exports = function (err, res, send, done) { + + if (err) return done(err) + + var ex = tar.extract() + res.pipe(ex) + + var objStream = new Readable({ objectMode: true }) + objStream._read = function noop () {} + + ex.on('entry', function (header, stream, next) { + objStream.push({ + path: header.name, + content: header.type !== 'directory' ? stream : null + }) + next() + }) + ex.on('finish', () => { + objStream.push(null) + }) + + done(null, objStream) +} + diff --git a/test/api/get.spec.js b/test/api/get.spec.js new file mode 100644 index 000000000..30f9a8479 --- /dev/null +++ b/test/api/get.spec.js @@ -0,0 +1,125 @@ +/* eslint-env mocha */ +/* globals apiClients */ +'use strict' + +const expect = require('chai').expect +const isNode = require('detect-node') +const fs = require('fs') +const bl = require('bl') +const concat = require('concat-stream') +const through = require('through2') + +const path = require('path') +const streamEqual = require('stream-equal') + +const extract = require('tar-stream').extract + +let testfile +let testfileBig + +if (isNode) { + testfile = fs.readFileSync(path.join(__dirname, '/../testfile.txt')) + testfileBig = fs.createReadStream(path.join(__dirname, '/../15mb.random'), { bufferSize: 128 }) +} else { + testfile = require('raw!../testfile.txt') +} + +describe('.get', () => { + it('get with no compression args', (done) => { + apiClients.a + .get('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', (err, res) => { + + // accumulate the files and their content + var files = [] + res.pipe(through.obj((file, enc, next) => { + file.content.pipe(concat((content) => { + files.push({ + path: file.path, + content: content + }) + next() + })) + }, () => { + expect(files).to.be.length(1) + expect(files[0].content.toString()).to.contain(testfile.toString()) + done() + })) + }) + }) + + it('get with archive true', (done) => { + apiClients.a + .get('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {archive: true}, (err, res) => { + + // accumulate the files and their content + var files = [] + res.pipe(through.obj((file, enc, next) => { + file.content.pipe(concat((content) => { + files.push({ + path: file.path, + content: content + }) + next() + })) + }, () => { + expect(files).to.be.length(1) + expect(files[0].content.toString()).to.contain(testfile.toString()) + done() + })) + }) + }) + + it('get err with out of range compression level', (done) => { + apiClients.a + .get('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {compress: true, 'compression-level': 10}, (err, res) => { + expect(err).to.exist + expect(err.toString()).to.equal('Error: Compression level must be between 1 and 9') + done() + }) + }) + + it('get with compression level', (done) => { + apiClients.a + .get('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {compress: true, 'compression-level': 1}, (err, res) => { + expect(err).to.not.exist + done() + }) + }) + + it.skip('get BIG file', (done) => { + if (!isNode) { + return done() + } + + apiClients.a.get('Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq', (err, res) => { + expect(err).to.not.exist + + // Do not blow out the memory of nodejs :) + streamEqual(res, testfileBig, (err, equal) => { + expect(err).to.not.exist + expect(equal).to.be.true + done() + }) + }) + }) + + describe('promise', () => { + it.skip('get', (done) => { + return apiClients.a.get('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + .then((res) => { + let buf = '' + res + .on('error', (err) => { + throw err + }) + .on('data', (data) => { + buf += data + }) + .on('end', () => { + expect(buf).to.contain(testfile.toString()) + done() + }) + }) + }) + }) +})