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

Commit f542d77

Browse files
committed
Adds "files add" http route.
1 parent 761f2c9 commit f542d77

File tree

7 files changed

+239
-3
lines changed

7 files changed

+239
-3
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"lodash.get": "^4.3.0",
7979
"lodash.set": "^4.2.0",
8080
"multiaddr": "^2.0.0",
81+
"multihashes": "^0.2.2",
8182
"path-exists": "^3.0.0",
8283
"peer-book": "^0.1.1",
8384
"peer-id": "^0.6.6",
@@ -122,4 +123,4 @@
122123
"kumavis <[email protected]>",
123124
"nginnever <[email protected]>"
124125
]
125-
}
126+
}

src/http-api/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ exports = module.exports = function HttpApi (repo) {
1919

2020
this.start = (callback) => {
2121
if (typeof repo === 'string') {
22-
console.log('here')
2322
repo = new IPFSRepo(repo, {stores: fsbs})
2423
}
2524

src/http-api/resources/files.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
'use strict'
22

33
const bs58 = require('bs58')
4+
const multihash = require('multihashes')
5+
const ndjson = require('ndjson')
6+
const multipart = require('ipfs-multipart')
47
const debug = require('debug')
58
const log = debug('http-api:files')
69
log.error = debug('http-api:files:error')
@@ -48,3 +51,60 @@ exports.cat = {
4851
})
4952
}
5053
}
54+
55+
exports.add = {
56+
handler: (request, reply) => {
57+
if (!request.payload) {
58+
return reply('Array, Buffer, or String is required.').code(400).takeover()
59+
}
60+
61+
const parser = multipart.reqParser(request.payload)
62+
63+
var filesParsed = false
64+
var filesAdded = 0
65+
66+
var serialize = ndjson.serialize()
67+
// hapi doesn't permit object streams: http://hapijs.com/api#replyerr-result
68+
serialize._readableState.objectMode = false
69+
70+
var fileAdder = request.server.app.ipfs.files.add()
71+
72+
fileAdder.on('data', (file) => {
73+
serialize.write({
74+
Name: file.path,
75+
Hash: multihash.toB58String(file.multihash)
76+
})
77+
filesAdded++
78+
})
79+
80+
fileAdder.on('end', () => {
81+
if (filesAdded === 0 && filesParsed) {
82+
return reply({
83+
Message: 'Failed to add files.',
84+
Code: 0
85+
}).code(500)
86+
} else {
87+
serialize.end()
88+
return reply(serialize)
89+
.header('x-chunked-output', '1')
90+
.header('content-type', 'application/json')
91+
}
92+
})
93+
94+
parser.on('file', (fileName, fileStream) => {
95+
var filePair = {
96+
path: fileName,
97+
stream: fileStream
98+
}
99+
filesParsed = true
100+
fileAdder.write(filePair)
101+
})
102+
103+
parser.on('end', () => {
104+
if (!filesParsed) {
105+
return reply("File argument 'data' is required.").code(400).takeover()
106+
}
107+
fileAdder.end()
108+
})
109+
}
110+
}

src/http-api/routes/files.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,16 @@ module.exports = (server) => {
1515
handler: resources.files.cat.handler
1616
}
1717
})
18+
19+
api.route({
20+
method: '*',
21+
path: '/api/v0/add',
22+
config: {
23+
payload: {
24+
parse: false,
25+
output: 'stream'
26+
},
27+
handler: resources.files.add.handler
28+
}
29+
})
1830
}

src/http-api/routes/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = (server) => {
88
require('./object')(server)
99
// require('./repo')(server)
1010
require('./config')(server)
11+
require('./files')(server)
1112
require('./swarm')(server)
1213
require('./bitswap')(server)
13-
require('./files')(server)
1414
}

test/http-api-tests/test-files.js

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
const expect = require('chai').expect
55
const APIctl = require('ipfs-api')
66

7+
const fs = require('fs')
8+
const FormData = require('form-data')
9+
const streamToPromise = require('stream-to-promise')
10+
const Readable = require('stream').Readable
11+
const http = require('http')
12+
13+
function singleFileServer (filename) {
14+
return http.createServer(function (req, res) {
15+
fs.createReadStream(filename).pipe(res)
16+
})
17+
}
18+
19+
720
module.exports = (httpAPI) => {
821
describe('files', () => {
922
describe('api', () => {
@@ -83,4 +96,155 @@ module.exports = (httpAPI) => {
8396
})
8497
})
8598
})
99+
100+
describe('files', () => {
101+
describe('api', () => {
102+
let api
103+
104+
it('api', () => {
105+
api = httpAPI.server.select('API')
106+
})
107+
108+
describe('/files/add', () => {
109+
it('returns 400 if no tuple is provided', (done) => {
110+
const form = new FormData()
111+
const headers = form.getHeaders()
112+
113+
streamToPromise(form).then((payload) => {
114+
api.inject({
115+
method: 'POST',
116+
url: '/api/v0/add',
117+
headers: headers,
118+
payload: payload
119+
}, (res) => {
120+
expect(res.statusCode).to.equal(400)
121+
done()
122+
})
123+
})
124+
})
125+
126+
it('adds a file', (done) => {
127+
const form = new FormData()
128+
const filePath = 'test/test-data/node.json'
129+
form.append('file', fs.createReadStream(filePath))
130+
const headers = form.getHeaders()
131+
132+
streamToPromise(form).then((payload) => {
133+
api.inject({
134+
method: 'POST',
135+
url: '/api/v0/add',
136+
headers: headers,
137+
payload: payload
138+
}, (res) => {
139+
expect(res.statusCode).to.equal(200)
140+
var result = JSON.parse(res.result)
141+
expect(result.Name).to.equal('node.json')
142+
expect(result.Hash).to.equal('QmRRdjTN2PjyEPrW73GBxJNAZrstH5tCZzwHYFJpSTKkhe')
143+
done()
144+
})
145+
})
146+
})
147+
148+
it('adds multiple files', (done) => {
149+
const form = new FormData()
150+
const filePath = 'test/test-data/hello'
151+
const filePath2 = 'test/test-data/otherconfig'
152+
form.append('file', fs.createReadStream(filePath))
153+
form.append('file', fs.createReadStream(filePath2))
154+
const headers = form.getHeaders()
155+
156+
streamToPromise(form).then((payload) => {
157+
api.inject({
158+
method: 'POST',
159+
url: '/api/v0/add',
160+
headers: headers,
161+
payload: payload
162+
}, (res) => {
163+
expect(res.statusCode).to.equal(200)
164+
var results = res.result.split('\n').slice(0, -1).map(JSON.parse)
165+
expect(results[0].Name).to.equal('hello')
166+
expect(results[0].Hash).to.equal('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
167+
expect(results[1].Name).to.equal('otherconfig')
168+
expect(results[1].Hash).to.equal('QmayedZNznnEbHtyfjeQvvt29opSLjYjLtLqwfwSWq28ds')
169+
done()
170+
})
171+
})
172+
})
173+
})
174+
})
175+
176+
describe('using js-ipfs-api', () => {
177+
var ctl
178+
179+
it('start IPFS API ctl', (done) => {
180+
ctl = APIctl('/ip4/127.0.0.1/tcp/6001')
181+
done()
182+
})
183+
184+
describe('ipfs.add', () => {
185+
it('adds two files under a chunk Size', (done) => {
186+
const rs = new Readable()
187+
const rs2 = new Readable()
188+
var files = []
189+
const buffered = fs.readFileSync('test/test-data/hello')
190+
const buffered2 = fs.readFileSync('test/test-data/otherconfig')
191+
rs.push(buffered)
192+
rs.push(null)
193+
rs2.push(buffered2)
194+
rs2.push(null)
195+
const filePair = {path: 'hello', content: rs}
196+
const filePair2 = {path: 'otherconfig', content: rs2}
197+
files.push(filePair)
198+
files.push(filePair2)
199+
200+
ctl.add(files, (err, res) => {
201+
expect(err).to.not.exist
202+
expect(res[0].Name).to.equal('hello')
203+
expect(res[0].Hash).to.equal('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')
204+
expect(res[1].Name).to.equal('otherconfig')
205+
expect(res[1].Hash).to.equal('QmayedZNznnEbHtyfjeQvvt29opSLjYjLtLqwfwSWq28ds')
206+
done()
207+
})
208+
})
209+
210+
it('adds a large file > a chunk', (done) => {
211+
const rs = new Readable()
212+
var files = []
213+
const buffered = fs.readFileSync('test/test-data/1.2MiB.txt')
214+
rs.push(buffered)
215+
rs.push(null)
216+
const filePair = {path: '1.2MiB.txt', content: rs}
217+
files.push(filePair)
218+
219+
ctl.add(filePair, (err, res) => {
220+
expect(err).to.not.exist
221+
expect(res[0].Name).to.equal('1.2MiB.txt')
222+
expect(res[0].Hash).to.equal('QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
223+
done()
224+
})
225+
})
226+
227+
it('adds a buffer', (done) => {
228+
const buffer = new Buffer('hello world')
229+
ctl.add(buffer, (err, res) => {
230+
expect(err).to.not.exist
231+
expect(res[0].Hash).to.equal('Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD')
232+
done()
233+
})
234+
})
235+
236+
it('adds a url', (done) => {
237+
var server = singleFileServer('test/test-data/1.2MiB.txt')
238+
server.listen(2913, function () {
239+
ctl.add('http://localhost:2913/', (err, res) => {
240+
expect(err).to.not.exist
241+
const added = res[0] != null ? res[0] : res
242+
expect(added).to.have.a.property('Hash', 'QmW7BDxEbGqxxSYVtn3peNPQgdDXbWkoQ6J1EFYAEuQV3Q')
243+
done()
244+
})
245+
})
246+
})
247+
})
248+
})
249+
})
86250
}

test/test-data/1.2MiB.txt

1.2 MB
Binary file not shown.

0 commit comments

Comments
 (0)