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

Commit 69681a7

Browse files
feat: pass file name to add/addAll progress handler (#3372)
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 <[email protected]>
1 parent 7a0f1a2 commit 69681a7

File tree

15 files changed

+178
-71
lines changed

15 files changed

+178
-71
lines changed

docs/core-api/FILES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ An optional object which may have the following keys:
252252
| hashAlg | `String` | `'sha2-256'` | multihash hashing algorithm to use |
253253
| onlyHash | `boolean` | `false` | If true, will not add blocks to the blockstore |
254254
| pin | `boolean` | `true` | pin this object when adding |
255-
| progress | function | `undefined` | a function that will be called with the byte length of chunks as a file is added to ipfs |
255+
| 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 |
256256
| rawLeaves | `boolean` | `false` | if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |
257257
| shardSplitThreshold | `Number` | `1000` | Directories with more than this number of files will be created as HAMT-sharded directories |
258258
| 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 |

packages/interface-ipfs-core/src/add-all.js

+20
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,26 @@ module.exports = (common, options) => {
170170
expect(root.cid.toString()).to.equal(fixtures.directory.cid)
171171
})
172172

173+
it('should receive file name from progress event', async () => {
174+
const receivedNames = []
175+
function handler (p, name) {
176+
receivedNames.push(name)
177+
}
178+
179+
await drain(ipfs.addAll([{
180+
content: 'hello',
181+
path: 'foo.txt'
182+
}, {
183+
content: 'world',
184+
path: 'bar.txt'
185+
}], {
186+
progress: handler,
187+
wrapWithDirectory: true
188+
}))
189+
190+
expect(receivedNames).to.deep.equal(['foo.txt', 'bar.txt'])
191+
})
192+
173193
it('should add files to a directory non sequentially', async function () {
174194
const content = path => ({
175195
path: `test-dir/${path}`,

packages/interface-ipfs-core/src/add.js

+14
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,20 @@ module.exports = (common, options) => {
130130
expect(accumProgress).to.equal(fixtures.emptyFile.data.length)
131131
})
132132

133+
it('should receive file name from progress event', async () => {
134+
let receivedName
135+
function handler (p, name) {
136+
receivedName = name
137+
}
138+
139+
await ipfs.add({
140+
content: 'hello',
141+
path: 'foo.txt'
142+
}, { progress: handler })
143+
144+
expect(receivedName).to.equal('foo.txt')
145+
})
146+
133147
it('should add an empty file without progress enabled', async () => {
134148
const file = await ipfs.add(fixtures.emptyFile.data)
135149

packages/ipfs-core/src/components/add-all/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ module.exports = ({ block, gcLock, preload, pin, options: constructorOptions })
4747
let total = 0
4848
const prog = opts.progress
4949

50-
opts.progress = (bytes) => {
50+
opts.progress = (bytes, fileName) => {
5151
total += bytes
52-
prog(total)
52+
prog(total, fileName)
5353
}
5454
}
5555

@@ -162,8 +162,8 @@ function pinFile (pin, opts) {
162162
* @property {boolean} [onlyHash=false] - If true, will not add blocks to the
163163
* blockstore.
164164
* @property {boolean} [pin=true] - Pin this object when adding.
165-
* @property {(bytes:number) => void} [progress] - A function that will be
166-
* called with the byte length of chunks as a file is added to ipfs.
165+
* @property {(bytes:number, fileName:string) => void} [progress] - A function that will be
166+
* called with the number of bytes added as a file is added to ipfs and the name of the file being added.
167167
* @property {boolean} [rawLeaves=false] - If true, DAG leaves will contain raw
168168
* file data and not be wrapped in a protobuf.
169169
* @property {number} [shardSplitThreshold=1000] - Directories with more than this

packages/ipfs-http-client/src/add-all.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = configure((api) => {
3838
if (file.hash !== undefined) {
3939
yield toCoreInterface(file)
4040
} else if (progressFn) {
41-
progressFn(file.bytes || 0)
41+
progressFn(file.bytes || 0, file.name)
4242
}
4343
}
4444
}

packages/ipfs-http-client/src/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ module.exports = ipfsClient
6868
* derives API from it's return type and extends it last `options` parameter
6969
* with `HttpOptions`.
7070
*
71-
* This can be used to avoid (re)typing API interface when implemeting it in
71+
* This can be used to avoid (re)typing API interface when implementing it in
7272
* http client e.g you can annotate `ipfs.addAll` implementation with
7373
*
7474
* `@type {Implements<typeof import('ipfs-core/src/components/add-all')>}`
@@ -83,7 +83,7 @@ module.exports = ipfsClient
8383
/**
8484
* @template Key
8585
* @template {(config:any) => any} APIFactory
86-
* @typedef {import('./interface').APIMethadWithExtraOptions<ReturnType<APIFactory>, Key, HttpOptions>} ImplementsMethod
86+
* @typedef {import('./interface').APIMethodWithExtraOptions<ReturnType<APIFactory>, Key, HttpOptions>} ImplementsMethod
8787
*/
8888

8989
/**

packages/ipfs-http-client/src/interface.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file contains some utility types that either can't be expressed in
2-
// JSDoc syntax or that result in a different behavior when typed in JSDoc.
2+
// JSDoc syntax or that result in a different behaviour when typed in JSDoc.
33

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

54-
export type APIMethadWithExtraOptions <
54+
export type APIMethodWithExtraOptions <
5555
API,
5656
Key extends keyof API,
5757
Extra

packages/ipfs-message-port-client/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"cross-env": "^7.0.0",
5151
"interface-ipfs-core": "^0.141.0",
5252
"ipfs": "^0.51.0",
53+
"ipfs-core": "^0.1.0",
5354
"ipfs-message-port-protocol": "^0.3.0",
5455
"ipfs-message-port-server": "^0.3.0",
5556
"ipld-dag-pb": "^0.20.0",

packages/ipfs-message-port-client/src/core.js

+3-42
Original file line numberDiff line numberDiff line change
@@ -73,30 +73,7 @@ class CoreClient extends Client {
7373
* `transfer: [input.buffer]` which would allow transferring it instead of
7474
* copying.
7575
*
76-
* @param {AddAllInput} input
77-
* @param {Object} [options]
78-
* @param {string} [options.chunker="size-262144"]
79-
* @param {number} [options.cidVersion=0]
80-
* @param {boolean} [options.enableShardingExperiment]
81-
* @param {string} [options.hashAlg="sha2-256"]
82-
* @param {boolean} [options.onlyHash=false]
83-
* @param {boolean} [options.pin=true]
84-
* @param {function(number):void} [options.progress]
85-
* @param {boolean} [options.rawLeaves=false]
86-
* @param {number} [options.shardSplitThreshold=1000]
87-
* @param {boolean} [options.trickle=false]
88-
* @param {boolean} [options.wrapWithDirectory=false]
89-
* @param {number} [options.timeout]
90-
* @param {Transferable[]} [options.transfer]
91-
* @param {AbortSignal} [options.signal]
92-
* @returns {AsyncIterable<AddedData>}
93-
*
94-
* @typedef {Object} AddedData
95-
* @property {string} path
96-
* @property {CID} cid
97-
* @property {number} mode
98-
* @property {number} size
99-
* @property {Time} mtime
76+
* @type {import('.').Implements<typeof import('ipfs-core/src/components/add-all')>}
10077
*/
10178
async * addAll (input, options = {}) {
10279
const { timeout, signal } = options
@@ -123,23 +100,7 @@ class CoreClient extends Client {
123100
* `transfer: [input.buffer]` which would allow transferring it instead of
124101
* copying.
125102
*
126-
* @param {AddInput} input
127-
* @param {Object} [options]
128-
* @param {string} [options.chunker="size-262144"]
129-
* @param {number} [options.cidVersion=0]
130-
* @param {boolean} [options.enableShardingExperiment]
131-
* @param {string} [options.hashAlg="sha2-256"]
132-
* @param {boolean} [options.onlyHash=false]
133-
* @param {boolean} [options.pin=true]
134-
* @param {function(number):void} [options.progress]
135-
* @param {boolean} [options.rawLeaves=false]
136-
* @param {number} [options.shardSplitThreshold=1000]
137-
* @param {boolean} [options.trickle=false]
138-
* @param {boolean} [options.wrapWithDirectory=false]
139-
* @param {number} [options.timeout]
140-
* @param {Transferable[]} [options.transfer]
141-
* @param {AbortSignal} [options.signal]
142-
* @returns {Promise<AddedData>}
103+
* @type {import('.').Implements<typeof import('ipfs-core/src/components/add')>}
143104
*/
144105
async add (input, options = {}) {
145106
const { timeout, signal } = options
@@ -200,7 +161,7 @@ class CoreClient extends Client {
200161
* Decodes values yield by `ipfs.add`.
201162
*
202163
* @param {AddedEntry} data
203-
* @returns {AddedData}
164+
* @returns {import('ipfs-core/src/components/add-all').UnixFSEntry}
204165
*/
205166
const decodeAddedData = ({ path, cid, mode, mtime, size }) => {
206167
return {

packages/ipfs-message-port-client/src/index.js

+36
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,39 @@ class IPFSClient extends CoreClient {
6262
}
6363

6464
module.exports = IPFSClient
65+
66+
/**
67+
* @typedef {Object} MessagePortOptions
68+
* @property {Array} [transfer] - A list of ArrayBuffers whose ownership will be transferred to the shared worker
69+
*
70+
* @typedef {import('ipfs-core/src/utils').AbortOptions} AbortOptions}
71+
*/
72+
73+
/**
74+
* This is an utility type that can be used to derive type of the HTTP Client
75+
* API from the Core API. It takes type of the API factory (from ipfs-core),
76+
* derives API from it's return type and extends it last `options` parameter
77+
* with `HttpOptions`.
78+
*
79+
* This can be used to avoid (re)typing API interface when implementing it in
80+
* http client e.g you can annotate `ipfs.addAll` implementation with
81+
*
82+
* `@type {Implements<typeof import('ipfs-core/src/components/add-all')>}`
83+
*
84+
* **Caution**: This supports APIs with up to four parameters and last optional
85+
* `options` parameter, anything else will result to `never` type.
86+
*
87+
* @template {(config:any) => any} APIFactory
88+
* @typedef {APIWithExtraOptions<ReturnType<APIFactory>, MessagePortOptions>} Implements
89+
*/
90+
91+
/**
92+
* @template Key
93+
* @template {(config:any) => any} APIFactory
94+
* @typedef {import('./interface').APIMethodWithExtraOptions<ReturnType<APIFactory>, Key, MessagePortOptions>} ImplementsMethod
95+
*/
96+
97+
/**
98+
* @template API, Extra
99+
* @typedef {import('./interface').APIWithExtraOptions<API, Extra>} APIWithExtraOptions
100+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// This file contains some utility types that either can't be expressed in
2+
// JSDoc syntax or that result in a different behaviour when typed in JSDoc.
3+
4+
/**
5+
* Utility type that takes IPFS Core API function type (with 0 to 4 arguments
6+
* & last **optional** `options` parameter) and derives a function type with
7+
* `options` parameter extended with given `Extra` options.
8+
*
9+
* **Caution**: API Functions with more than for arguments ahead of `options`
10+
* will result to `never` type. API function that does not take `options` will
11+
* result in function whose last argument is extended with `Extra` which would
12+
* be an error.
13+
*/
14+
// This is typed in TS file because otherwise TS unifies on the first parameter
15+
// regardless of number of parameters function has.
16+
export type APIWithExtraOptions<API extends (...args: any[]) => any, Extra> =
17+
(...args: WithExtendedOptions<Parameters<API>, Extra>) => ReturnType<API>
18+
19+
type End = never[]
20+
type WithExtendedOptions<Params, Ext> = Params extends [...End]
21+
? []
22+
// (options?: Options) -> (options?: Options & Ext)
23+
: Params extends [options?: infer Options, ...end: End]
24+
? [options?: Options & Ext]
25+
// (a: A1, options?: Options) -> (a1: A1, options?: Options & Ext)
26+
: Params extends [a1: infer A1, options?: infer Options, ...end: End]
27+
? [a1: A1, options?: Options & Ext]
28+
// (a1?: A1, options?: Options) -> (a1?: A1, options?: Options & Ext)
29+
: Params extends [a1?: infer A1, options?: infer Options, ...end: End]
30+
? [a1?: A1, options?: Options & Ext]
31+
// (a1: A1, a2: A2, options?: Options) -> (a1: A1, a2: A2 options?: Options & Ext)
32+
: Params extends [a1: infer A1, a2: infer A2, options?: infer Options, ...end: End]
33+
? [a1: A1, a2: A2, options?: Options & Ext]
34+
// (a1: A1, a2?: A2, options?: Options) -> (a1: A1, a2?: A2 options?: Options & Ext)
35+
: Params extends [a1: infer A1, a2?: infer A2, options?: infer Options, ...end: End]
36+
? [a1: A1, a2?: A2, options?: Options & Ext]
37+
// (a1: A1, a2?: A2, options?: Options) -> (a1: A1, a2?: A2 options?: Options & Ext)
38+
: Params extends [a1?: infer A1, a2?: infer A2, options?: infer Options, ...end: End]
39+
? [a1?: A1, a2?: A2, options?: Options & Ext]
40+
// (a1: A1, a2: A2, a3:A3 options?: Options) -> (a1: A1, a2: A2, a3:A3, options?: Options & Ext)
41+
: Params extends [a1: infer A1, a2: infer A2, a3:infer A3, options?: infer Options, ...end: End]
42+
? [a1: A1, a2: A2, a3: A3, options?: Options & Ext]
43+
// (a1: A1, a2: A2, a3?:A3 options?: Options) -> (a1: A1, a2: A2, a3?:A3, options?: Options & Ext)
44+
: Params extends [a1: infer A1, a2:infer A2, a3?: infer A3, options?: infer Options, ...end: End]
45+
? [a1: A1, a2: A2, a3?: A3, options?: Options & Ext]
46+
// (a1: A1, a2?: A2, a3?:A3 options?: Options) -> (a1: A1, a2?: A2, a3?:A3, options?: Options & Ext)
47+
: Params extends [a1: infer A1, a2?: infer A2, a3?: infer A3, options?: infer Options, ...end: End]
48+
? [a1: A1, a2?: A2, a3?: A3, options?: Options & Ext]
49+
// (a1?: A1, a2?: A2, a3?:A3 options?: Options) -> (a1?: A1, a2?: A2, a3?:A3, options?: Options & Ext)
50+
: Params extends [a1?: infer A1, a2?: infer A2, a3?: infer A3, options?: infer Options, ...end: End]
51+
? [a1?: A1, a2?: A2, a3?: A3, options?: Options & Ext]
52+
: never
53+
54+
export type APIMethodWithExtraOptions <
55+
API,
56+
Key extends keyof API,
57+
Extra
58+
> = API[Key] extends (...args: any[]) => any ? APIWithExtraOptions<API[Key], Extra> : never

packages/ipfs-message-port-client/tsconfig.json

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
},
1414
{
1515
"path": "../ipfs-message-port-server"
16+
},
17+
{
18+
"path": "../ipfs-core"
19+
},
20+
{
21+
"path": "../ipfs-core-utils"
1622
}
1723
]
1824
}

packages/ipfs-message-port-protocol/src/core.js

+8-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const { encodeError, decodeError } = require('./error')
1111
*/
1212

1313
/**
14-
* @template T
1514
* @typedef {Object} RemoteCallback
1615
* @property {'RemoteCallback'} type
1716
* @property {MessagePort} port
@@ -173,33 +172,32 @@ const toIterator = iterable => {
173172
}
174173

175174
/**
176-
* @template T
177-
* @param {function(T):void} callback
175+
* @param {Function} callback
178176
* @param {Transferable[]} transfer
179-
* @returns {RemoteCallback<T>}
177+
* @returns {RemoteCallback}
180178
*/
181179
const encodeCallback = (callback, transfer) => {
182180
// eslint-disable-next-line no-undef
183181
const { port1: port, port2: remote } = new MessageChannel()
184-
port.onmessage = ({ data }) => callback(data)
182+
port.onmessage = ({ data }) => callback.apply(null, data)
185183
transfer.push(remote)
186184
return { type: 'RemoteCallback', port: remote }
187185
}
188186
exports.encodeCallback = encodeCallback
189187

190188
/**
191189
* @template T
192-
* @param {RemoteCallback<T>} remote
193-
* @returns {function(T):void | function(T, Transferable[]):void}
190+
* @param {RemoteCallback} remote
191+
* @returns {function(T[]):void | function(T[], Transferable[]):void}
194192
*/
195193
const decodeCallback = ({ port }) => {
196194
/**
197-
* @param {T} value
195+
* @param {T[]} args
198196
* @param {Transferable[]} [transfer]
199197
* @returns {void}
200198
*/
201-
const callback = (value, transfer = []) => {
202-
port.postMessage(value, transfer)
199+
const callback = (args, transfer = []) => {
200+
port.postMessage(args, transfer)
203201
}
204202

205203
return callback

packages/ipfs-message-port-protocol/test/core.browser.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ describe('core', function () {
3232
await move(encodeCallback(callback, transfer), transfer)
3333
)
3434

35-
remote(54)
35+
remote([54])
3636
expect(await receive()).to.be.equal(54)
3737

38-
remote({ hello: 'world' })
38+
remote([{ hello: 'world' }])
3939

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

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

6161
const world = uint8ArrayFromString('world')
62-
remote({ hello: world }, [world.buffer])
62+
remote([{ hello: world }], [world.buffer])
6363

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

0 commit comments

Comments
 (0)