diff --git a/src/core/ipfs/files.js b/src/core/ipfs/files.js index 03e8689fcc..db8ea19494 100644 --- a/src/core/ipfs/files.js +++ b/src/core/ipfs/files.js @@ -127,8 +127,8 @@ module.exports = function files (self) { }), get: promisify((hash, callback) => { - const exportFile = Exporter(hash, self._dagS) - callback(null, exportFile) + const exportStream = Exporter(hash, self._dagS) + callback(null, exportStream) }) } } diff --git a/src/core/ipfs/object.js b/src/core/ipfs/object.js index f1ab133a88..1649f7b8fa 100644 --- a/src/core/ipfs/object.js +++ b/src/core/ipfs/object.js @@ -155,7 +155,6 @@ module.exports = function object (self) { if (err) { return cb(err) } - cb(null, node.data) }) }), diff --git a/src/http-api/resources/files.js b/src/http-api/resources/files.js index f4b8f14dd2..46cff0093c 100644 --- a/src/http-api/resources/files.js +++ b/src/http-api/resources/files.js @@ -1,9 +1,13 @@ 'use strict' const bs58 = require('bs58') +const ndjson = require('ndjson') +const multipart = require('ipfs-multipart') const debug = require('debug') +const tar = require('tar-stream') const log = debug('http-api:files') log.error = debug('http-api:files:error') +const async = require('async') exports = module.exports @@ -42,8 +46,126 @@ exports.cat = { Code: 0 }).code(500) } + return reply(stream).header('X-Stream-Output', '1') + }) + } +} + +exports.get = { + // uses common parseKey method that returns a `key` + parseArgs: exports.parseKey, + + // 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 + + request.server.app.ipfs.files.get(key, (err, stream) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to get file: ' + err, + Code: 0 + }).code(500) + } + var pack = tar.pack() + const files = [] stream.on('data', (data) => { - return reply(data) + files.push(data) + }) + const processFile = (file) => { + return (callback) => { + if (!file.content) { // is directory + pack.entry({name: file.path, type: 'directory'}) + callback() + } else { // is file + const fileContents = [] + file.content.on('data', (data) => { + fileContents.push(data) + }) + file.content.on('end', () => { + pack.entry({name: file.path}, Buffer.concat(fileContents)) + callback() + }) + } + } + } + stream.on('end', () => { + const callbacks = files.map(processFile) + async.series(callbacks, () => { + pack.finalize() + reply(pack).header('X-Stream-Output', '1') + }) + }) + }) + } +} + +exports.add = { + handler: (request, reply) => { + if (!request.payload) { + return reply('Array, Buffer, or String is required.').code(400).takeover() + } + + const parser = multipart.reqParser(request.payload) + + var filesParsed = false + var filesAdded = 0 + + var serialize = ndjson.serialize() + // hapi doesn't permit object streams: http://hapijs.com/api#replyerr-result + serialize._readableState.objectMode = false + + request.server.app.ipfs.files.createAddStream((err, fileAdder) => { + if (err) { + return reply({ + Message: err, + Code: 0 + }).code(500) + } + + fileAdder.on('data', (file) => { + const filePath = file.path ? file.path : file.hash + serialize.write({ + Name: filePath, + Hash: file.hash + }) + filesAdded++ + }) + + fileAdder.on('end', () => { + if (filesAdded === 0 && filesParsed) { + return reply({ + Message: 'Failed to add files.', + Code: 0 + }).code(500) + } else { + serialize.end() + return reply(serialize) + .header('x-chunked-output', '1') + .header('content-type', 'application/json') + } + }) + + parser.on('file', (fileName, fileStream) => { + var filePair = { + path: fileName, + content: fileStream + } + filesParsed = true + fileAdder.write(filePair) + }) + parser.on('directory', (directory) => { + fileAdder.write({ + path: directory, + content: '' + }) + }) + + parser.on('end', () => { + if (!filesParsed) { + return reply("File argument 'data' is required.").code(400).takeover() + } + fileAdder.end() }) }) } diff --git a/src/http-api/routes/files.js b/src/http-api/routes/files.js index 99c47741e3..da57b3f2f1 100644 --- a/src/http-api/routes/files.js +++ b/src/http-api/routes/files.js @@ -6,6 +6,7 @@ module.exports = (server) => { const api = server.select('API') api.route({ + // TODO fix method method: '*', path: '/api/v0/cat', config: { @@ -15,4 +16,29 @@ module.exports = (server) => { handler: resources.files.cat.handler } }) + + api.route({ + // TODO fix method + method: '*', + path: '/api/v0/get', + config: { + pre: [ + { method: resources.files.get.parseArgs, assign: 'args' } + ], + handler: resources.files.get.handler + } + }) + + api.route({ + // TODO fix method + method: '*', + path: '/api/v0/add', + config: { + payload: { + parse: false, + output: 'stream' + }, + handler: resources.files.add.handler + } + }) } diff --git a/test/http-api/interface-ipfs-core-over-ipfs-api/test-files.js b/test/http-api/interface-ipfs-core-over-ipfs-api/test-files.js index 4a322b948e..4762a26f76 100644 --- a/test/http-api/interface-ipfs-core-over-ipfs-api/test-files.js +++ b/test/http-api/interface-ipfs-core-over-ipfs-api/test-files.js @@ -2,7 +2,6 @@ 'use strict' -/* const test = require('interface-ipfs-core') const FactoryClient = require('./../../utils/factory-http') @@ -17,8 +16,5 @@ const common = { fc.dismantle(callback) } } -*/ -// TODO -// needs: https://github.com/ipfs/js-ipfs/pull/323 -// test.files(common) +test.files(common)