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

Commit f6dcb0f

Browse files
achingbrainAlan Shaw
authored and
Alan Shaw
committed
fix: make progress bar work again when adding files (#2386)
Pulled out of #2379. BREAKING CHANGE: Order of output from the CLI command `ipfs add` may change between invocations given the same files since it is not longer buffered and sorted. Previously, js-ipfs buffered all hashes of added files and sorted them before outputting to the terminal, this might be because the `glob` module does not return stable results. This gives a poor user experience as you see nothing then everything, compared to `go-ipfs` which prints out the hashes as they become available. The tradeoff is the order might be slightly different between invocations, though the hashes are always the same as we sort DAGNode links before serializing so it doesn't matter what order you import them in.
1 parent 44045b0 commit f6dcb0f

File tree

2 files changed

+60
-41
lines changed

2 files changed

+60
-41
lines changed

src/cli/commands/add.js

+52-39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict'
22

3-
const pull = require('pull-stream')
3+
const pull = require('pull-stream/pull')
4+
const through = require('pull-stream/throughs/through')
5+
const end = require('pull-stream/sinks/on-end')
46
const promisify = require('promisify-es6')
57
const getFolderSize = promisify(require('get-folder-size'))
68
const byteman = require('byteman')
@@ -11,17 +13,35 @@ const { createProgressBar } = require('../utils')
1113
const { cidToString } = require('../../utils/cid')
1214
const globSource = require('../../utils/files/glob-source')
1315

14-
async function getTotalBytes (paths, cb) {
16+
async function getTotalBytes (paths) {
1517
const sizes = await Promise.all(paths.map(p => getFolderSize(p)))
1618
return sizes.reduce((total, size) => total + size, 0)
1719
}
1820

19-
function addPipeline (source, addStream, options) {
21+
function addPipeline (source, addStream, options, log) {
22+
let finalHash
23+
2024
return new Promise((resolve, reject) => {
2125
pull(
2226
source,
2327
addStream,
24-
pull.collect((err, added) => {
28+
through((file) => {
29+
const cid = finalHash = cidToString(file.hash, { base: options.cidBase })
30+
31+
if (options.silent || options.quieter) {
32+
return
33+
}
34+
35+
let message = cid
36+
37+
if (!options.quiet) {
38+
// print the hash twice if we are piping from stdin
39+
message = `added ${cid} ${options.file ? file.path || '' : cid}`.trim()
40+
}
41+
42+
log(message)
43+
}),
44+
end((err) => {
2545
if (err) {
2646
// Tweak the error message and add more relevant infor for the CLI
2747
if (err.code === 'ERR_DIR_NON_RECURSIVE') {
@@ -30,32 +50,10 @@ function addPipeline (source, addStream, options) {
3050
return reject(err)
3151
}
3252

33-
if (options.silent) return resolve()
34-
3553
if (options.quieter) {
36-
options.print(added.pop().hash)
37-
return resolve()
54+
log(finalHash)
3855
}
3956

40-
added
41-
.sort((a, b) => {
42-
if (a.path > b.path) {
43-
return 1
44-
}
45-
if (a.path < b.path) {
46-
return -1
47-
}
48-
return 0
49-
})
50-
.reverse()
51-
.map((file) => {
52-
const log = options.quiet ? [] : ['added']
53-
log.push(cidToString(file.hash, { base: options.cidBase }))
54-
if (!options.quiet && file.path.length > 0) log.push(file.path)
55-
return log.join(' ')
56-
})
57-
.forEach((msg) => options.print(msg))
58-
5957
resolve()
6058
})
6159
)
@@ -179,25 +177,40 @@ module.exports = {
179177
throw new Error('Error: Enabling the sharding experiment should be done on the daemon')
180178
}
181179

180+
let bar
181+
let log = argv.print
182+
183+
if (argv.quieter || argv.quiet || argv.silent) {
184+
argv.progress = false
185+
}
186+
187+
if (argv.progress && argv.file) {
188+
const totalBytes = await getTotalBytes(argv.file)
189+
bar = createProgressBar(totalBytes)
190+
191+
if (process.stdout.isTTY) {
192+
// bar.interrupt uses clearLine and cursorTo methods that are only on TTYs
193+
log = bar.interrupt.bind(bar)
194+
}
195+
196+
options.progress = byteLength => {
197+
bar.update(byteLength / totalBytes, { progress: byteman(byteLength, 2, 'MB') })
198+
}
199+
}
200+
182201
const source = argv.file
183202
? globSource(...argv.file, { recursive: argv.recursive })
184203
: toPull.source(process.stdin) // Pipe directly to ipfs.add
185204

186205
const adder = ipfs.addPullStream(options)
187206

188-
// No progress or piping directly to ipfs.add: no need to getTotalBytes
189-
if (!argv.progress || !argv.file) {
190-
return addPipeline(source, adder, argv)
191-
}
192-
193-
const totalBytes = await getTotalBytes(argv.file)
194-
const bar = createProgressBar(totalBytes)
195-
196-
options.progress = byteLength => {
197-
bar.update(byteLength / totalBytes, { progress: byteman(byteLength, 2, 'MB') })
207+
try {
208+
await addPipeline(source, adder, argv, log)
209+
} finally {
210+
if (bar) {
211+
bar.terminate()
212+
}
198213
}
199-
200-
return addPipeline(source, adder, argv)
201214
})())
202215
}
203216
}

test/cli/files.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ describe('files', () => runOnAndOff((thing) => {
170170

171171
return ipfs('add -r test/fixtures/test-data/recursive-get-dir')
172172
.then((out) => {
173-
expect(out).to.eql(recursiveGetDirResults.join('\n') + '\n')
173+
// glob module does not return stable matches
174+
recursiveGetDirResults.forEach(line => { // eslint-disable-line max-nested-callbacks
175+
expect(out).to.include(line)
176+
})
174177
})
175178
})
176179

@@ -179,7 +182,10 @@ describe('files', () => runOnAndOff((thing) => {
179182

180183
return ipfs('add -r test/fixtures/test-data/recursive-get-dir/')
181184
.then((out) => {
182-
expect(out).to.eql(recursiveGetDirResults.join('\n') + '\n')
185+
// glob module does not return stable matches
186+
recursiveGetDirResults.forEach(line => { // eslint-disable-line max-nested-callbacks
187+
expect(out).to.include(line)
188+
})
183189
})
184190
})
185191

0 commit comments

Comments
 (0)