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

refactor: fetch add #1063

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6c17081
feat: enable pubsub in the browser
alanshaw Jul 25, 2019
0974b73
fix: tests
alanshaw Jul 25, 2019
0f3e640
fix: use included querystring module
alanshaw Jul 25, 2019
850e1fb
chore: appease linter
alanshaw Jul 25, 2019
4de1e09
chore: update interface-ipfs-core
alanshaw Jul 25, 2019
2437cf4
chore: skip test
alanshaw Jul 25, 2019
4a89c6a
fix: skip in the right place
alanshaw Jul 25, 2019
b656aab
refactor: more readable code for consuming message stream
alanshaw Jul 25, 2019
e6f97d3
fix: add workaround for subscribe in Firefox
alanshaw Jul 26, 2019
d84716a
refactor: use promise-nodeify
alanshaw Jul 26, 2019
684b2a1
test: add tests for lib fns
alanshaw Jul 26, 2019
176556c
chore: appease linter
alanshaw Jul 26, 2019
cea1bd5
fix: tests in browser
alanshaw Jul 26, 2019
636d302
perf: use URLSearchParams in the browser
alanshaw Jul 29, 2019
62529c3
chore: appease linter
alanshaw Jul 29, 2019
b58c489
fix: oops, removed more code than I should have
alanshaw Jul 29, 2019
70653ad
refactor: use ky
alanshaw Jul 30, 2019
e600d98
fix: configure tests
alanshaw Jul 30, 2019
29647db
chore: appease linter
alanshaw Jul 30, 2019
f973a13
feat: add, addPullStream, addFromFs and addFromStream
alanshaw Jul 29, 2019
625a6a2
refactor: use ky
alanshaw Jul 30, 2019
c0ef769
refactor: convert addReadableStream
alanshaw Aug 3, 2019
916efbd
refactor: convert addFromFs
alanshaw Aug 3, 2019
c213336
refactor: remove unused code
alanshaw Aug 3, 2019
9b302b8
fix: tests
alanshaw Aug 4, 2019
8481fb6
fix: tests
alanshaw Aug 4, 2019
cb5e2b0
fix: normalize input for iterable of buffer or blob
alanshaw Aug 4, 2019
6192ef0
Merge branch 'master' into refactor/fetch-add
alanshaw Aug 28, 2019
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
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"browser": {
"glob": false,
"fs": false,
"stream": "readable-stream"
"stream": "readable-stream",
"./src/add/form-data.js": "./src/add/form-data.browser.js",
"./src/add-from-fs/index.js": "./src/add-from-fs/index.browser.js"
},
"repository": "github:ipfs/js-ipfs-http-client",
"scripts": {
Expand All @@ -35,6 +37,7 @@
"dependencies": {
"abort-controller": "^3.0.0",
"async": "^2.6.1",
"async-iterator-to-pull-stream": "^1.3.0",
"bignumber.js": "^9.0.0",
"bl": "^3.0.0",
"bs58": "^4.0.1",
Expand All @@ -58,6 +61,8 @@
"is-stream": "^2.0.0",
"iso-stream-http": "~0.1.2",
"iso-url": "~0.4.6",
"it-pushable": "^1.2.1",
"it-to-stream": "^0.1.1",
"iterable-ndjson": "^1.1.0",
"just-kebab-case": "^1.1.0",
"just-map-keys": "^1.1.0",
Expand All @@ -77,6 +82,7 @@
"promisify-es6": "^1.0.3",
"pull-defer": "~0.2.3",
"pull-stream": "^3.6.9",
"pull-stream-to-async-iterator": "^1.0.2",
"pull-to-stream": "~0.1.1",
"pump": "^3.0.0",
"qs": "^6.5.2",
Expand Down
94 changes: 94 additions & 0 deletions src/add-from-fs/glob-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict'

const Fs = require('fs')
const Path = require('path')
const glob = require('glob')
const pushable = require('it-pushable')
const errCode = require('err-code')

/**
* Create an AsyncIterable that can be passed to ipfs.add for the
* provided file paths.
*
* @param {String} ...paths File system path(s) to glob from
* @param {Object} [options] Optional options
* @param {Boolean} [options.recursive] Recursively glob all paths in directories
* @param {Boolean} [options.hidden] Include .dot files in matched paths
* @param {Array<String>} [options.ignore] Glob paths to ignore
* @param {Boolean} [options.followSymlinks] follow symlinks
* @returns {AsyncIterable}
*/
module.exports = (...args) => (async function * () {
const options = typeof args[args.length - 1] === 'string' ? {} : args.pop()
const paths = args

const globSourceOptions = {
recursive: options.recursive,
glob: {
dot: Boolean(options.hidden),
ignore: Array.isArray(options.ignore) ? options.ignore : [],
follow: options.followSymlinks != null ? options.followSymlinks : true
}
}

// Check the input paths comply with options.recursive and convert to glob sources
const results = await Promise.all(paths.map(pathAndType))
const globSources = results.map(r => toGlobSource(r, globSourceOptions))

for (const globSource of globSources) {
for await (const { path, contentPath } of globSource) {
yield { path, content: Fs.createReadStream(contentPath) }
}
}
})()

function toGlobSource ({ path, type }, options) {
return (async function * () {
options = options || {}

const baseName = Path.basename(path)

if (type === 'file') {
yield { path: baseName, contentPath: path }
return
}

if (type === 'dir' && !options.recursive) {
throw errCode(
new Error(`'${path}' is a directory and recursive option not set`),
'ERR_DIR_NON_RECURSIVE',
{ path }
)
}

const globOptions = Object.assign({}, options.glob, {
cwd: path,
nodir: true,
realpath: false,
absolute: false
})

// TODO: want to use pull-glob but it doesn't have the features...
const pusher = pushable()

glob('**/*', globOptions)
.on('match', m => pusher.push(m))
.on('end', () => pusher.end())
.on('abort', () => pusher.end())
.on('error', err => pusher.end(err))

for await (const p of pusher) {
yield {
path: `${baseName}/${toPosix(p)}`,
contentPath: Path.join(path, p)
}
}
})()
}

async function pathAndType (path) {
const stat = await Fs.promises.stat(path)
return { path, type: stat.isDirectory() ? 'dir' : 'file' }
}

const toPosix = path => path.replace(/\\/g, '/')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

3 changes: 3 additions & 0 deletions src/add-from-fs/index.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict'

module.exports = () => () => { throw new Error('unavailable in the browser') }
9 changes: 9 additions & 0 deletions src/add-from-fs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

const configure = require('../lib/configure')
const globSource = require('./glob-source')

module.exports = configure(({ ky }) => {
const add = require('../add')({ ky })
return (path, options) => add(globSource(path, options), options)
})
24 changes: 24 additions & 0 deletions src/add-from-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict'

const kyDefault = require('ky-universal').default
const configure = require('./lib/configure')
const toIterable = require('./lib/stream-to-iterable')

module.exports = configure(({ ky }) => {
const add = require('./add')({ ky })

return (url, options) => (async function * () {
options = options || {}

const { body } = await kyDefault.get(url)

const input = {
path: decodeURIComponent(new URL(url).pathname.split('/').pop() || ''),
content: toIterable(body)
}

for await (const file of add(input, options)) {
yield file
}
})()
})
30 changes: 30 additions & 0 deletions src/add/form-data.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'
/* eslint-env browser */

const normaliseInput = require('./normalise-input')

exports.toFormData = async (input) => {
const files = normaliseInput(input)
const formData = new FormData()
let i = 0

for await (const file of files) {
if (file.content) {
// In the browser there's _currently_ no streaming upload, buffer up our
// async iterator chunks and append a big Blob :(
// One day, this will be browser streams
const bufs = []
for await (const chunk of file.content) {
bufs.push(Buffer.isBuffer(chunk) ? chunk.buffer : chunk)
}

formData.append(`file-${i}`, new Blob(bufs, { type: 'application/octet-stream' }), file.path)
} else {
formData.append(`dir-${i}`, new Blob([], { type: 'application/x-directory' }), file.path)
}

i++
}

return formData
}
42 changes: 42 additions & 0 deletions src/add/form-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict'

const FormData = require('form-data')
const { Buffer } = require('buffer')
const toStream = require('it-to-stream')
const normaliseInput = require('./normalise-input')

exports.toFormData = async (input) => {
const files = normaliseInput(input)
const formData = new FormData()
let i = 0

for await (const file of files) {
if (file.content) {
// In Node.js, FormData can be passed a stream so no need to buffer
formData.append(
`file-${i}`,
// FIXME: add a `path` property to the stream so `form-data` doesn't set
// a Content-Length header that is only the sum of the size of the
// header/footer when knownLength option (below) is null.
Object.assign(
toStream.readable(file.content),
{ path: file.path || `file-${i}` }
),
{
filepath: encodeURIComponent(file.path),
contentType: 'application/octet-stream',
knownLength: file.content.length // Send Content-Length header if known
}
)
} else {
formData.append(`dir-${i}`, Buffer.alloc(0), {
filepath: encodeURIComponent(file.path),
contentType: 'application/x-directory'
})
}

i++
}

return formData
}
54 changes: 54 additions & 0 deletions src/add/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

const ndjson = require('iterable-ndjson')
const configure = require('../lib/configure')
const toIterable = require('../lib/stream-to-iterable')
const { toFormData } = require('./form-data')
const toCamel = require('../lib/object-to-camel')

module.exports = configure(({ ky }) => {
return (input, options) => (async function * () {
options = options || {}

const searchParams = new URLSearchParams(options.searchParams)

searchParams.set('stream-channels', true)
if (options.chunker) searchParams.set('chunker', options.chunker)
if (options.cidVersion) searchParams.set('cid-version', options.cidVersion)
if (options.cidBase) searchParams.set('cid-base', options.cidBase)
if (options.enableShardingExperiment != null) searchParams.set('enable-sharding-experiment', options.enableShardingExperiment)
if (options.hashAlg) searchParams.set('hash', options.hashAlg)
if (options.onlyHash != null) searchParams.set('only-hash', options.onlyHash)
if (options.pin != null) searchParams.set('pin', options.pin)
if (options.progress) searchParams.set('progress', true)
if (options.quiet != null) searchParams.set('quiet', options.quiet)
if (options.quieter != null) searchParams.set('quieter', options.quieter)
if (options.rawLeaves != null) searchParams.set('raw-leaves', options.rawLeaves)
if (options.shardSplitThreshold) searchParams.set('shard-split-threshold', options.shardSplitThreshold)
if (options.silent) searchParams.set('silent', options.silent)
if (options.trickle != null) searchParams.set('trickle', options.trickle)
if (options.wrapWithDirectory != null) searchParams.set('wrap-with-directory', options.wrapWithDirectory)

const res = await ky.post('add', {
timeout: options.timeout,
signal: options.signal,
headers: options.headers,
searchParams,
body: await toFormData(input)
})

for await (let file of ndjson(toIterable(res.body))) {
file = toCamel(file)
// console.log(file)
if (options.progress && file.bytes) {
options.progress(file.bytes)
} else {
yield toCoreInterface(file)
}
}
})()
})

function toCoreInterface ({ name, hash, size }) {
return { path: name, hash, size: parseInt(size) }
}
Loading