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

Adds "files add" http route. #235

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"lodash.get": "^4.3.0",
"lodash.set": "^4.2.0",
"multiaddr": "^2.0.0",
"multihashes": "^0.2.2",
"path-exists": "^3.0.0",
"peer-book": "^0.1.1",
"peer-id": "^0.6.6",
Expand Down Expand Up @@ -122,4 +123,4 @@
"kumavis <[email protected]>",
"nginnever <[email protected]>"
]
}
}
1 change: 0 additions & 1 deletion src/http-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ exports = module.exports = function HttpApi (repo) {

this.start = (callback) => {
if (typeof repo === 'string') {
console.log('here')
repo = new IPFSRepo(repo, {stores: fsbs})
}

Expand Down
60 changes: 60 additions & 0 deletions src/http-api/resources/files.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict'

const bs58 = require('bs58')
const multihash = require('multihashes')
const ndjson = require('ndjson')
const multipart = require('ipfs-multipart')
const debug = require('debug')
const log = debug('http-api:files')
log.error = debug('http-api:files:error')
Expand Down Expand Up @@ -48,3 +51,60 @@ exports.cat = {
})
}
}

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

var fileAdder = request.server.app.ipfs.files.add()

fileAdder.on('data', (file) => {
serialize.write({
Name: file.path,
Hash: multihash.toB58String(file.multihash)
})
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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't fileStream already a readable stream? do we need to copy it?

//cc @xicombd

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this -- seems like it was unnecessary!

var filePair = {
path: fileName,
stream: fileStream
}
filesParsed = true
fileAdder.write(filePair)
})

parser.on('end', () => {
if (!filesParsed) {
return reply("File argument 'data' is required.").code(400).takeover()
}
fileAdder.end()
})
}
}
12 changes: 12 additions & 0 deletions src/http-api/routes/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ module.exports = (server) => {
handler: resources.files.cat.handler
}
})

api.route({
method: '*',
path: '/api/v0/add',
config: {
payload: {
parse: false,
output: 'stream'
},
handler: resources.files.add.handler
}
})
}
2 changes: 1 addition & 1 deletion src/http-api/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = (server) => {
require('./object')(server)
// require('./repo')(server)
require('./config')(server)
require('./files')(server)
require('./swarm')(server)
require('./bitswap')(server)
require('./files')(server)
}
2 changes: 1 addition & 1 deletion test/cli-tests/test-bitswap.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const createTempNode = require('../utils/temp-node')
const repoPath = require('./index').repoPath

describe('bitswap', function () {
this.timeout(20000)
this.timeout(80000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this an env variable? Seems weird to keep defining it everywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are often different depending on the test, that's why they are set specifically in here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this unrelated change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could break this into another PR, but timeouts were making my local tests fail. :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make it at least a separate commit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they already are -- see 5d55b3c. Did I miss one?

const env = _.clone(process.env)
env.IPFS_PATH = repoPath

Expand Down
2 changes: 1 addition & 1 deletion test/cli-tests/test-swarm.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const repoPath = require('./index').repoPath
const _ = require('lodash')

describe('swarm', function () {
this.timeout(20000)
this.timeout(80000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this unrelated change?

const env = _.clone(process.env)
env.IPFS_PATH = repoPath

Expand Down
2 changes: 1 addition & 1 deletion test/core-tests/test-swarm-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const expect = require('chai').expect
const createTempNode = require('../utils/temp-node')

describe('swarm', function () {
this.timeout(20000)
this.timeout(80000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this unrelated change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahah, saying it once is enough, I think :)


var ipfsA
var ipfsB
Expand Down
2 changes: 1 addition & 1 deletion test/http-api-tests/test-bitswap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const expect = require('chai').expect

module.exports = (httpAPI) => {
describe('bitswap', function () {
this.timeout(20000)
this.timeout(80000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this unrelated change?

describe('commands', () => {
let api
before(() => {
Expand Down
164 changes: 164 additions & 0 deletions test/http-api-tests/test-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
const expect = require('chai').expect
const APIctl = require('ipfs-api')

const fs = require('fs')
const FormData = require('form-data')
const streamToPromise = require('stream-to-promise')
const Readable = require('stream').Readable
const http = require('http')

function singleFileServer (filename) {
return http.createServer(function (req, res) {
fs.createReadStream(filename).pipe(res)
})
}


module.exports = (httpAPI) => {
describe('files', () => {
describe('api', () => {
Expand Down Expand Up @@ -83,4 +96,155 @@ module.exports = (httpAPI) => {
})
})
})

describe('files', () => {
describe('api', () => {
let api

it('api', () => {
api = httpAPI.server.select('API')
})

describe('/files/add', () => {
it('returns 400 if no tuple is provided', (done) => {
const form = new FormData()
const headers = form.getHeaders()

streamToPromise(form).then((payload) => {
api.inject({
method: 'POST',
url: '/api/v0/add',
headers: headers,
payload: payload
}, (res) => {
expect(res.statusCode).to.equal(400)
done()
})
})
})

it('adds a file', (done) => {
const form = new FormData()
const filePath = 'test/test-data/node.json'
form.append('file', fs.createReadStream(filePath))
const headers = form.getHeaders()

streamToPromise(form).then((payload) => {
api.inject({
method: 'POST',
url: '/api/v0/add',
headers: headers,
payload: payload
}, (res) => {
expect(res.statusCode).to.equal(200)
var result = JSON.parse(res.result)
expect(result.Name).to.equal('node.json')
expect(result.Hash).to.equal('QmRRdjTN2PjyEPrW73GBxJNAZrstH5tCZzwHYFJpSTKkhe')
done()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should check the returned hash

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

})
})
})

it('adds multiple files', (done) => {
const form = new FormData()
const filePath = 'test/test-data/hello'
const filePath2 = 'test/test-data/otherconfig'
form.append('file', fs.createReadStream(filePath))
form.append('file', fs.createReadStream(filePath2))
const headers = form.getHeaders()

streamToPromise(form).then((payload) => {
api.inject({
method: 'POST',
url: '/api/v0/add',
headers: headers,
payload: payload
}, (res) => {
expect(res.statusCode).to.equal(200)
var results = res.result.split('\n').slice(0, -1).map(JSON.parse)
expect(results[0].Name).to.equal('hello')
expect(results[0].Hash).to.equal('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
expect(results[1].Name).to.equal('otherconfig')
expect(results[1].Hash).to.equal('QmayedZNznnEbHtyfjeQvvt29opSLjYjLtLqwfwSWq28ds')
done()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should check the returned hash

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

})
})
})
})
})

describe('using js-ipfs-api', () => {
var ctl

it('start IPFS API ctl', (done) => {
ctl = APIctl('/ip4/127.0.0.1/tcp/6001')
done()
})

describe('ipfs.add', () => {
it('adds two files under a chunk Size', (done) => {
const rs = new Readable()
const rs2 = new Readable()
var files = []
const buffered = fs.readFileSync('test/test-data/hello')
const buffered2 = fs.readFileSync('test/test-data/otherconfig')
rs.push(buffered)
rs.push(null)
rs2.push(buffered2)
rs2.push(null)
const filePair = {path: 'hello', content: rs}
const filePair2 = {path: 'otherconfig', content: rs2}
files.push(filePair)
files.push(filePair2)

ctl.add(files, (err, res) => {
expect(err).to.not.exist
expect(res[0].Name).to.equal('hello')
expect(res[0].Hash).to.equal('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
expect(res[1].Name).to.equal('otherconfig')
expect(res[1].Hash).to.equal('QmayedZNznnEbHtyfjeQvvt29opSLjYjLtLqwfwSWq28ds')
done()
})
})

it('adds a large file > a chunk', (done) => {
const rs = new Readable()
var files = []
const buffered = fs.readFileSync('test/test-data/1.2MiB.txt')
rs.push(buffered)
rs.push(null)
const filePair = {path: '1.2MiB.txt', content: rs}
files.push(filePair)

ctl.add(filePair, (err, res) => {
expect(err).to.not.exist
expect(res[0].Name).to.equal('1.2MiB.txt')
expect(res[0].Hash).to.equal('QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
done()
})
})

it('adds a buffer', (done) => {
const buffer = new Buffer('hello world')
ctl.add(buffer, (err, res) => {
expect(err).to.not.exist
expect(res[0].Hash).to.equal('Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD')
done()
})
})

it('adds a url', (done) => {
var server = singleFileServer('test/test-data/1.2MiB.txt')
server.listen(2913, function () {
ctl.add('http://localhost:2913/', (err, res) => {
expect(err).to.not.exist
const added = res[0] != null ? res[0] : res
expect(added).to.have.a.property('Hash', 'QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
done()
})
})
})
})
})
})
}
2 changes: 1 addition & 1 deletion test/http-api-tests/test-swarm.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const createTempNode = require('../utils/temp-node')

module.exports = (httpAPI) => {
describe('swarm', function () {
this.timeout(20000)
this.timeout(80000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this unrelated change?


describe('api', () => {
var api
Expand Down
Binary file added test/test-data/1.2MiB.txt
Binary file not shown.