Skip to content
This repository was archived by the owner on Mar 10, 2020. It is now read-only.

Commit 56a89f1

Browse files
author
Alan Shaw
committed
feat: add, addPullStream, addFromFs and addFromStream
License: MIT Signed-off-by: Alan Shaw <alan.shaw@protocol.ai>
1 parent b58c489 commit 56a89f1

16 files changed

+558
-143
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"fs": false,
2020
"stream": "readable-stream",
2121
"./src/lib/configure.js": "./src/lib/configure.browser.js",
22-
"./src/lib/querystring.js": "./src/lib/querystring.browser.js"
22+
"./src/lib/querystring.js": "./src/lib/querystring.browser.js",
23+
"./src/add/form-data.js": "./src/add/form-data.browser.js"
2324
},
2425
"repository": "github:ipfs/js-ipfs-http-client",
2526
"scripts": {
@@ -37,6 +38,7 @@
3738
"dependencies": {
3839
"abort-controller": "^3.0.0",
3940
"async": "^2.6.1",
41+
"async-iterator-to-pull-stream": "^1.3.0",
4042
"bignumber.js": "^9.0.0",
4143
"bl": "^3.0.0",
4244
"bs58": "^4.0.1",
@@ -78,6 +80,7 @@
7880
"promisify-es6": "^1.0.3",
7981
"pull-defer": "~0.2.3",
8082
"pull-stream": "^3.6.9",
83+
"pull-stream-to-async-iterator": "^1.0.2",
8184
"pull-to-stream": "~0.1.1",
8285
"pump": "^3.0.0",
8386
"qs": "^6.5.2",

src/add-from-url.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
const configure = require('./lib/configure')
4+
const { ok, toIterable } = require('./lib/fetch')
5+
6+
module.exports = configure(({ fetch, apiAddr, apiPath, headers }) => {
7+
const add = require('./add')({ fetch, apiAddr, apiPath, headers })
8+
9+
return (url, options) => (async function * () {
10+
options = options || {}
11+
const res = await ok(fetch(url, {
12+
signal: options.signal,
13+
headers: options.headers || headers
14+
}))
15+
16+
const input = {
17+
path: decodeURIComponent(new URL(url).pathname.split('/').pop() || ''),
18+
content: toIterable(res.body)
19+
}
20+
21+
for await (const file of add(input, options)) {
22+
yield file
23+
}
24+
})()
25+
})

src/add/form-data.browser.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict'
2+
/* eslint-env browser */
3+
4+
const normaliseInput = require('./normalise-input')
5+
6+
exports.toFormData = async (input) => {
7+
const files = normaliseInput(input)
8+
const formData = new FormData()
9+
let i = 0
10+
11+
for await (const file of files) {
12+
if (file.content) {
13+
// In the browser there's _currently_ no streaming upload, buffer up our
14+
// async iterator chunks and append a big Blob :(
15+
// One day, this will be browser streams
16+
const bufs = []
17+
for await (const chunk of file.content) {
18+
bufs.push(Buffer.isBuffer(chunk) ? chunk.buffer : chunk)
19+
}
20+
21+
formData.append(`file-${i}`, new Blob(bufs, { type: 'application/octet-stream' }), file.path)
22+
} else {
23+
formData.append(`dir-${i}`, new Blob([], { type: 'application/x-directory' }), file.path)
24+
}
25+
26+
i++
27+
}
28+
29+
return formData
30+
}

src/add/form-data.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict'
2+
3+
const FormData = require('form-data')
4+
const { Buffer } = require('buffer')
5+
const normaliseInput = require('./normalise-input')
6+
const toStream = require('../lib/iterable-to-readable-stream')
7+
8+
exports.toFormData = async (input) => {
9+
const files = normaliseInput(input)
10+
const formData = new FormData()
11+
let i = 0
12+
13+
for await (const file of files) {
14+
if (file.content) {
15+
// In Node.js, FormData can be passed a stream so no need to buffer
16+
formData.append(
17+
`file-${i}`,
18+
// FIXME: add a `path` property to the stream so `form-data` doesn't set
19+
// a Content-Length header that is only the sum of the size of the
20+
// header/footer when knownLength option (below) is null.
21+
Object.assign(
22+
toStream(file.content),
23+
{ path: file.path || `file-${i}` }
24+
),
25+
{
26+
filepath: file.path,
27+
contentType: 'application/octet-stream',
28+
knownLength: file.content.length // Send Content-Length header if known
29+
}
30+
)
31+
} else {
32+
formData.append(`dir-${i}`, Buffer.alloc(0), {
33+
filepath: file.path,
34+
contentType: 'application/x-directory'
35+
})
36+
}
37+
38+
i++
39+
}
40+
41+
return formData
42+
}

src/add/index.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict'
2+
3+
const ndjson = require('iterable-ndjson')
4+
const { objectToQuery } = require('../lib/querystring')
5+
const configure = require('../lib/configure')
6+
const { ok, toIterable } = require('../lib/fetch')
7+
const { toFormData } = require('./form-data')
8+
const toCamel = require('../lib/object-to-camel')
9+
10+
module.exports = configure(({ fetch, apiAddr, apiPath, headers }) => {
11+
return (input, options) => (async function * () {
12+
options = options || {}
13+
14+
const qs = objectToQuery({
15+
'stream-channels': true,
16+
chunker: options.chunker,
17+
'cid-version': options.cidVersion,
18+
'cid-base': options.cidBase,
19+
'enable-sharding-experiment': options.enableShardingExperiment,
20+
hash: options.hashAlg,
21+
'only-hash': options.onlyHash,
22+
pin: options.pin,
23+
progress: options.progress ? true : null,
24+
quiet: options.quiet,
25+
quieter: options.quieter,
26+
'raw-leaves': options.rawLeaves,
27+
'shard-split-threshold': options.shardSplitThreshold,
28+
silent: options.silent,
29+
trickle: options.trickle,
30+
'wrap-with-directory': options.wrapWithDirectory,
31+
...(options.qs || {})
32+
})
33+
34+
const url = `${apiAddr}${apiPath}/add${qs}`
35+
const res = await ok(fetch(url, {
36+
method: 'POST',
37+
signal: options.signal,
38+
headers: options.headers || headers,
39+
body: await toFormData(input)
40+
}))
41+
42+
for await (let file of ndjson(toIterable(res.body))) {
43+
file = toCamel(file)
44+
// console.log(file)
45+
if (options.progress && file.bytes) {
46+
options.progress(file.bytes)
47+
} else {
48+
yield toCoreInterface(file)
49+
}
50+
}
51+
})()
52+
})
53+
54+
function toCoreInterface ({ name, hash, size }) {
55+
return { path: name, hash, size: parseInt(size) }
56+
}

src/add/normalise-input.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
'use strict'
2+
/* eslint-env browser */
3+
4+
const { Buffer } = require('buffer')
5+
const errCode = require('err-code')
6+
const toAsyncIterable = require('../lib/file-data-to-async-iterable')
7+
8+
/*
9+
Transform one of:
10+
11+
Buffer|ArrayBuffer|TypedArray
12+
Blob|File
13+
{ path, content: Buffer }
14+
{ path, content: Blob }
15+
{ path, content: Iterable<Buffer> }
16+
{ path, content: AsyncIterable<Buffer> }
17+
{ path, content: PullStream<Buffer> }
18+
Iterable<Number>
19+
Iterable<{ path, content: Buffer }>
20+
Iterable<{ path, content: Blob }>
21+
Iterable<{ path, content: Iterable<Number> }>
22+
Iterable<{ path, content: AsyncIterable<Buffer> }>
23+
Iterable<{ path, content: PullStream<Buffer> }>
24+
AsyncIterable<Buffer>
25+
AsyncIterable<{ path, content: Buffer }>
26+
AsyncIterable<{ path, content: Blob }>
27+
AsyncIterable<{ path, content: Iterable<Buffer> }>
28+
AsyncIterable<{ path, content: AsyncIterable<Buffer> }>
29+
AsyncIterable<{ path, content: PullStream<Buffer> }>
30+
PullStream<Buffer>
31+
32+
Into:
33+
34+
AsyncIterable<{ path, content: AsyncIterable<Buffer> }>
35+
*/
36+
37+
module.exports = function normalizeInput (input) {
38+
// Buffer|ArrayBuffer|TypedArray
39+
if (Buffer.isBuffer(input) || ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
40+
return (async function * () { // eslint-disable-line require-await
41+
yield normalizeTuple({ path: '', content: input })
42+
})()
43+
}
44+
45+
// Blob|File
46+
if (typeof Blob !== 'undefined' && input instanceof Blob) {
47+
return (async function * () { // eslint-disable-line require-await
48+
yield normalizeTuple({ path: '', content: input })
49+
})()
50+
}
51+
52+
// Iterable<Number>
53+
// Iterable<{ path, content: Buffer }>
54+
// Iterable<{ path, content: Blob }>
55+
// Iterable<{ path, content: Iterable<Number> }>
56+
// Iterable<{ path, content: AsyncIterable<Buffer> }>
57+
// Iterable<{ path, content: PullStream<Buffer> }>
58+
if (input[Symbol.iterator]) {
59+
return (async function * () { // eslint-disable-line require-await
60+
for (const chunk of input) {
61+
if (typeof chunk === 'object' && (chunk.path || chunk.content)) {
62+
yield normalizeTuple(chunk)
63+
} else if (Number.isInteger(chunk)) { // Must be an Iterable<Number> i.e. Buffer/ArrayBuffer/Array of bytes
64+
yield normalizeTuple({ path: '', content: input })
65+
return
66+
} else {
67+
throw errCode(new Error('Unexpected input: ' + typeof chunk), 'ERR_UNEXPECTED_INPUT')
68+
}
69+
}
70+
})()
71+
}
72+
73+
// AsyncIterable<Buffer>
74+
// AsyncIterable<{ path, content: Buffer }>
75+
// AsyncIterable<{ path, content: Blob }>
76+
// AsyncIterable<{ path, content: Iterable<Buffer> }>
77+
// AsyncIterable<{ path, content: AsyncIterable<Buffer> }>
78+
// AsyncIterable<{ path, content: PullStream<Buffer> }>
79+
if (input[Symbol.asyncIterator]) {
80+
return (async function * () {
81+
for await (const chunk of input) {
82+
if (typeof chunk === 'object' && (chunk.path || chunk.content)) {
83+
yield normalizeTuple(chunk)
84+
} else { // Must be an AsyncIterable<Buffer> i.e. a Stream
85+
let path = ''
86+
87+
// fs.createReadStream will create a stream with a `path` prop
88+
// If available, use it here!
89+
if (input.path && input.path.split) {
90+
path = input.path.split(/[/\\]/).pop() || ''
91+
}
92+
93+
yield normalizeTuple({
94+
path,
95+
content: (async function * () {
96+
yield chunk
97+
for await (const restChunk of input) {
98+
yield restChunk
99+
}
100+
})()
101+
})
102+
return
103+
}
104+
}
105+
})()
106+
}
107+
108+
// { path, content: Buffer }
109+
// { path, content: Blob }
110+
// { path, content: Iterable<Buffer> }
111+
// { path, content: AsyncIterable<Buffer> }
112+
// { path, content: PullStream<Buffer> }
113+
if (typeof input === 'object' && (input.path || input.content)) {
114+
// eslint-disable-next-line require-await
115+
return (async function * () { yield normalizeTuple(input) })()
116+
}
117+
118+
// PullStream
119+
if (typeof input === 'function') {
120+
return (async function * () { // eslint-disable-line require-await
121+
yield normalizeTuple({ path: '', content: input })
122+
})()
123+
}
124+
125+
throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')
126+
}
127+
128+
function normalizeTuple ({ path, content }) {
129+
return { path: path || '', content: content ? toAsyncIterable(content) : null }
130+
}

src/files-regular/add-from-url.js

Lines changed: 0 additions & 70 deletions
This file was deleted.

src/files-regular/add-pull-stream.js

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)