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

Commit

Permalink
feat: pass file name to add/addAll progress handler (#3372)
Browse files Browse the repository at this point in the history
Since ipfs/js-ipfs-unixfs#87 landed we can now pass the file name to
the progress handler for adding files:

```js
await ipfs.addAll(..., {
  progress: (bytes, fileName) => {
    //...
  }
})
```

This should make showing progress a bit more usable.

Co-authored-by: Hugo Dias <hugomrdias@gmail.com>
  • Loading branch information
achingbrain and hugomrdias authored Nov 6, 2020
1 parent 7a0f1a2 commit 69681a7
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 71 deletions.
2 changes: 1 addition & 1 deletion docs/core-api/FILES.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ An optional object which may have the following keys:
| hashAlg | `String` | `'sha2-256'` | multihash hashing algorithm to use |
| onlyHash | `boolean` | `false` | If true, will not add blocks to the blockstore |
| pin | `boolean` | `true` | pin this object when adding |
| progress | function | `undefined` | a function that will be called with the byte length of chunks as a file is added to ipfs |
| progress | function | `undefined` | a function that will be called with the number of bytes added as a file is added to ipfs and the name of the file being added |
| rawLeaves | `boolean` | `false` | if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |
| shardSplitThreshold | `Number` | `1000` | Directories with more than this number of files will be created as HAMT-sharded directories |
| trickle | `boolean` | `false` | if true will use the [trickle DAG](https://godoc.org/github.com/ipsn/go-ipfs/gxlibs/github.com/ipfs/go-unixfs/importer/trickle) format for DAG generation |
Expand Down
20 changes: 20 additions & 0 deletions packages/interface-ipfs-core/src/add-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ module.exports = (common, options) => {
expect(root.cid.toString()).to.equal(fixtures.directory.cid)
})

it('should receive file name from progress event', async () => {
const receivedNames = []
function handler (p, name) {
receivedNames.push(name)
}

await drain(ipfs.addAll([{
content: 'hello',
path: 'foo.txt'
}, {
content: 'world',
path: 'bar.txt'
}], {
progress: handler,
wrapWithDirectory: true
}))

expect(receivedNames).to.deep.equal(['foo.txt', 'bar.txt'])
})

it('should add files to a directory non sequentially', async function () {
const content = path => ({
path: `test-dir/${path}`,
Expand Down
14 changes: 14 additions & 0 deletions packages/interface-ipfs-core/src/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ module.exports = (common, options) => {
expect(accumProgress).to.equal(fixtures.emptyFile.data.length)
})

it('should receive file name from progress event', async () => {
let receivedName
function handler (p, name) {
receivedName = name
}

await ipfs.add({
content: 'hello',
path: 'foo.txt'
}, { progress: handler })

expect(receivedName).to.equal('foo.txt')
})

it('should add an empty file without progress enabled', async () => {
const file = await ipfs.add(fixtures.emptyFile.data)

Expand Down
8 changes: 4 additions & 4 deletions packages/ipfs-core/src/components/add-all/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ module.exports = ({ block, gcLock, preload, pin, options: constructorOptions })
let total = 0
const prog = opts.progress

opts.progress = (bytes) => {
opts.progress = (bytes, fileName) => {
total += bytes
prog(total)
prog(total, fileName)
}
}

Expand Down Expand Up @@ -162,8 +162,8 @@ function pinFile (pin, opts) {
* @property {boolean} [onlyHash=false] - If true, will not add blocks to the
* blockstore.
* @property {boolean} [pin=true] - Pin this object when adding.
* @property {(bytes:number) => void} [progress] - A function that will be
* called with the byte length of chunks as a file is added to ipfs.
* @property {(bytes:number, fileName:string) => void} [progress] - A function that will be
* called with the number of bytes added as a file is added to ipfs and the name of the file being added.
* @property {boolean} [rawLeaves=false] - If true, DAG leaves will contain raw
* file data and not be wrapped in a protobuf.
* @property {number} [shardSplitThreshold=1000] - Directories with more than this
Expand Down
2 changes: 1 addition & 1 deletion packages/ipfs-http-client/src/add-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = configure((api) => {
if (file.hash !== undefined) {
yield toCoreInterface(file)
} else if (progressFn) {
progressFn(file.bytes || 0)
progressFn(file.bytes || 0, file.name)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.exports = ipfsClient
* derives API from it's return type and extends it last `options` parameter
* with `HttpOptions`.
*
* This can be used to avoid (re)typing API interface when implemeting it in
* This can be used to avoid (re)typing API interface when implementing it in
* http client e.g you can annotate `ipfs.addAll` implementation with
*
* `@type {Implements<typeof import('ipfs-core/src/components/add-all')>}`
Expand All @@ -83,7 +83,7 @@ module.exports = ipfsClient
/**
* @template Key
* @template {(config:any) => any} APIFactory
* @typedef {import('./interface').APIMethadWithExtraOptions<ReturnType<APIFactory>, Key, HttpOptions>} ImplementsMethod
* @typedef {import('./interface').APIMethodWithExtraOptions<ReturnType<APIFactory>, Key, HttpOptions>} ImplementsMethod
*/

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// This file contains some utility types that either can't be expressed in
// JSDoc syntax or that result in a different behavior when typed in JSDoc.
// JSDoc syntax or that result in a different behaviour when typed in JSDoc.

/**
* Utility type that takes IPFS Core API function type (with 0 to 4 arguments
Expand Down Expand Up @@ -51,7 +51,7 @@ type WithExtendedOptions<Params, Ext> = Params extends [...End]
? [a1?: A1, a2?: A2, a3?: A3, options?: Options & Ext]
: never

export type APIMethadWithExtraOptions <
export type APIMethodWithExtraOptions <
API,
Key extends keyof API,
Extra
Expand Down
1 change: 1 addition & 0 deletions packages/ipfs-message-port-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"cross-env": "^7.0.0",
"interface-ipfs-core": "^0.141.0",
"ipfs": "^0.51.0",
"ipfs-core": "^0.1.0",
"ipfs-message-port-protocol": "^0.3.0",
"ipfs-message-port-server": "^0.3.0",
"ipld-dag-pb": "^0.20.0",
Expand Down
45 changes: 3 additions & 42 deletions packages/ipfs-message-port-client/src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,7 @@ class CoreClient extends Client {
* `transfer: [input.buffer]` which would allow transferring it instead of
* copying.
*
* @param {AddAllInput} input
* @param {Object} [options]
* @param {string} [options.chunker="size-262144"]
* @param {number} [options.cidVersion=0]
* @param {boolean} [options.enableShardingExperiment]
* @param {string} [options.hashAlg="sha2-256"]
* @param {boolean} [options.onlyHash=false]
* @param {boolean} [options.pin=true]
* @param {function(number):void} [options.progress]
* @param {boolean} [options.rawLeaves=false]
* @param {number} [options.shardSplitThreshold=1000]
* @param {boolean} [options.trickle=false]
* @param {boolean} [options.wrapWithDirectory=false]
* @param {number} [options.timeout]
* @param {Transferable[]} [options.transfer]
* @param {AbortSignal} [options.signal]
* @returns {AsyncIterable<AddedData>}
*
* @typedef {Object} AddedData
* @property {string} path
* @property {CID} cid
* @property {number} mode
* @property {number} size
* @property {Time} mtime
* @type {import('.').Implements<typeof import('ipfs-core/src/components/add-all')>}
*/
async * addAll (input, options = {}) {
const { timeout, signal } = options
Expand All @@ -123,23 +100,7 @@ class CoreClient extends Client {
* `transfer: [input.buffer]` which would allow transferring it instead of
* copying.
*
* @param {AddInput} input
* @param {Object} [options]
* @param {string} [options.chunker="size-262144"]
* @param {number} [options.cidVersion=0]
* @param {boolean} [options.enableShardingExperiment]
* @param {string} [options.hashAlg="sha2-256"]
* @param {boolean} [options.onlyHash=false]
* @param {boolean} [options.pin=true]
* @param {function(number):void} [options.progress]
* @param {boolean} [options.rawLeaves=false]
* @param {number} [options.shardSplitThreshold=1000]
* @param {boolean} [options.trickle=false]
* @param {boolean} [options.wrapWithDirectory=false]
* @param {number} [options.timeout]
* @param {Transferable[]} [options.transfer]
* @param {AbortSignal} [options.signal]
* @returns {Promise<AddedData>}
* @type {import('.').Implements<typeof import('ipfs-core/src/components/add')>}
*/
async add (input, options = {}) {
const { timeout, signal } = options
Expand Down Expand Up @@ -200,7 +161,7 @@ class CoreClient extends Client {
* Decodes values yield by `ipfs.add`.
*
* @param {AddedEntry} data
* @returns {AddedData}
* @returns {import('ipfs-core/src/components/add-all').UnixFSEntry}
*/
const decodeAddedData = ({ path, cid, mode, mtime, size }) => {
return {
Expand Down
36 changes: 36 additions & 0 deletions packages/ipfs-message-port-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,39 @@ class IPFSClient extends CoreClient {
}

module.exports = IPFSClient

/**
* @typedef {Object} MessagePortOptions
* @property {Array} [transfer] - A list of ArrayBuffers whose ownership will be transferred to the shared worker
*
* @typedef {import('ipfs-core/src/utils').AbortOptions} AbortOptions}
*/

/**
* This is an utility type that can be used to derive type of the HTTP Client
* API from the Core API. It takes type of the API factory (from ipfs-core),
* derives API from it's return type and extends it last `options` parameter
* with `HttpOptions`.
*
* This can be used to avoid (re)typing API interface when implementing it in
* http client e.g you can annotate `ipfs.addAll` implementation with
*
* `@type {Implements<typeof import('ipfs-core/src/components/add-all')>}`
*
* **Caution**: This supports APIs with up to four parameters and last optional
* `options` parameter, anything else will result to `never` type.
*
* @template {(config:any) => any} APIFactory
* @typedef {APIWithExtraOptions<ReturnType<APIFactory>, MessagePortOptions>} Implements
*/

/**
* @template Key
* @template {(config:any) => any} APIFactory
* @typedef {import('./interface').APIMethodWithExtraOptions<ReturnType<APIFactory>, Key, MessagePortOptions>} ImplementsMethod
*/

/**
* @template API, Extra
* @typedef {import('./interface').APIWithExtraOptions<API, Extra>} APIWithExtraOptions
*/
58 changes: 58 additions & 0 deletions packages/ipfs-message-port-client/src/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This file contains some utility types that either can't be expressed in
// JSDoc syntax or that result in a different behaviour when typed in JSDoc.

/**
* Utility type that takes IPFS Core API function type (with 0 to 4 arguments
* & last **optional** `options` parameter) and derives a function type with
* `options` parameter extended with given `Extra` options.
*
* **Caution**: API Functions with more than for arguments ahead of `options`
* will result to `never` type. API function that does not take `options` will
* result in function whose last argument is extended with `Extra` which would
* be an error.
*/
// This is typed in TS file because otherwise TS unifies on the first parameter
// regardless of number of parameters function has.
export type APIWithExtraOptions<API extends (...args: any[]) => any, Extra> =
(...args: WithExtendedOptions<Parameters<API>, Extra>) => ReturnType<API>

type End = never[]
type WithExtendedOptions<Params, Ext> = Params extends [...End]
? []
// (options?: Options) -> (options?: Options & Ext)
: Params extends [options?: infer Options, ...end: End]
? [options?: Options & Ext]
// (a: A1, options?: Options) -> (a1: A1, options?: Options & Ext)
: Params extends [a1: infer A1, options?: infer Options, ...end: End]
? [a1: A1, options?: Options & Ext]
// (a1?: A1, options?: Options) -> (a1?: A1, options?: Options & Ext)
: Params extends [a1?: infer A1, options?: infer Options, ...end: End]
? [a1?: A1, options?: Options & Ext]
// (a1: A1, a2: A2, options?: Options) -> (a1: A1, a2: A2 options?: Options & Ext)
: Params extends [a1: infer A1, a2: infer A2, options?: infer Options, ...end: End]
? [a1: A1, a2: A2, options?: Options & Ext]
// (a1: A1, a2?: A2, options?: Options) -> (a1: A1, a2?: A2 options?: Options & Ext)
: Params extends [a1: infer A1, a2?: infer A2, options?: infer Options, ...end: End]
? [a1: A1, a2?: A2, options?: Options & Ext]
// (a1: A1, a2?: A2, options?: Options) -> (a1: A1, a2?: A2 options?: Options & Ext)
: Params extends [a1?: infer A1, a2?: infer A2, options?: infer Options, ...end: End]
? [a1?: A1, a2?: A2, options?: Options & Ext]
// (a1: A1, a2: A2, a3:A3 options?: Options) -> (a1: A1, a2: A2, a3:A3, options?: Options & Ext)
: Params extends [a1: infer A1, a2: infer A2, a3:infer A3, options?: infer Options, ...end: End]
? [a1: A1, a2: A2, a3: A3, options?: Options & Ext]
// (a1: A1, a2: A2, a3?:A3 options?: Options) -> (a1: A1, a2: A2, a3?:A3, options?: Options & Ext)
: Params extends [a1: infer A1, a2:infer A2, a3?: infer A3, options?: infer Options, ...end: End]
? [a1: A1, a2: A2, a3?: A3, options?: Options & Ext]
// (a1: A1, a2?: A2, a3?:A3 options?: Options) -> (a1: A1, a2?: A2, a3?:A3, options?: Options & Ext)
: Params extends [a1: infer A1, a2?: infer A2, a3?: infer A3, options?: infer Options, ...end: End]
? [a1: A1, a2?: A2, a3?: A3, options?: Options & Ext]
// (a1?: A1, a2?: A2, a3?:A3 options?: Options) -> (a1?: A1, a2?: A2, a3?:A3, options?: Options & Ext)
: Params extends [a1?: infer A1, a2?: infer A2, a3?: infer A3, options?: infer Options, ...end: End]
? [a1?: A1, a2?: A2, a3?: A3, options?: Options & Ext]
: never

export type APIMethodWithExtraOptions <
API,
Key extends keyof API,
Extra
> = API[Key] extends (...args: any[]) => any ? APIWithExtraOptions<API[Key], Extra> : never
6 changes: 6 additions & 0 deletions packages/ipfs-message-port-client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
},
{
"path": "../ipfs-message-port-server"
},
{
"path": "../ipfs-core"
},
{
"path": "../ipfs-core-utils"
}
]
}
18 changes: 8 additions & 10 deletions packages/ipfs-message-port-protocol/src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const { encodeError, decodeError } = require('./error')
*/

/**
* @template T
* @typedef {Object} RemoteCallback
* @property {'RemoteCallback'} type
* @property {MessagePort} port
Expand Down Expand Up @@ -173,33 +172,32 @@ const toIterator = iterable => {
}

/**
* @template T
* @param {function(T):void} callback
* @param {Function} callback
* @param {Transferable[]} transfer
* @returns {RemoteCallback<T>}
* @returns {RemoteCallback}
*/
const encodeCallback = (callback, transfer) => {
// eslint-disable-next-line no-undef
const { port1: port, port2: remote } = new MessageChannel()
port.onmessage = ({ data }) => callback(data)
port.onmessage = ({ data }) => callback.apply(null, data)
transfer.push(remote)
return { type: 'RemoteCallback', port: remote }
}
exports.encodeCallback = encodeCallback

/**
* @template T
* @param {RemoteCallback<T>} remote
* @returns {function(T):void | function(T, Transferable[]):void}
* @param {RemoteCallback} remote
* @returns {function(T[]):void | function(T[], Transferable[]):void}
*/
const decodeCallback = ({ port }) => {
/**
* @param {T} value
* @param {T[]} args
* @param {Transferable[]} [transfer]
* @returns {void}
*/
const callback = (value, transfer = []) => {
port.postMessage(value, transfer)
const callback = (args, transfer = []) => {
port.postMessage(args, transfer)
}

return callback
Expand Down
8 changes: 4 additions & 4 deletions packages/ipfs-message-port-protocol/test/core.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ describe('core', function () {
await move(encodeCallback(callback, transfer), transfer)
)

remote(54)
remote([54])
expect(await receive()).to.be.equal(54)

remote({ hello: 'world' })
remote([{ hello: 'world' }])

expect(await receive()).to.be.deep.equal({ hello: 'world' })
})
Expand All @@ -55,11 +55,11 @@ describe('core', function () {
await move(encodeCallback(callback, transfer), transfer)
)

remote({ hello: uint8ArrayFromString('world') })
remote([{ hello: uint8ArrayFromString('world') }])
expect(await receive()).to.be.deep.equal({ hello: uint8ArrayFromString('world') })

const world = uint8ArrayFromString('world')
remote({ hello: world }, [world.buffer])
remote([{ hello: world }], [world.buffer])

expect(await receive()).to.be.deep.equal({ hello: uint8ArrayFromString('world') })
expect(world.buffer).property('byteLength', 0, 'buffer was cleared')
Expand Down
Loading

0 comments on commit 69681a7

Please sign in to comment.