status-bar
Advanced tools
Comparing version 0.0.2 to 1.0.0
255
lib/index.js
@@ -11,2 +11,52 @@ "use strict"; | ||
var storage = [" B ", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB", | ||
" YiB"]; | ||
var speeds = ["B/s", "K/s", "M/s", "G/s", "T/s", "P/s", "E/s", "Z/s", "Y/s"]; | ||
var space = function (n, max){ | ||
n += ""; | ||
var spaces = max - n.length; | ||
for (var i=0; i<spaces; i++){ | ||
n = " " + n; | ||
} | ||
return n; | ||
}; | ||
var unit = function (n, arr, pow, decimals){ | ||
var chars = decimals ? 5 + decimals : 4; | ||
if (n < pow) return space (n, chars) + arr[0]; | ||
var i = 1; | ||
while (i < 9){ | ||
n /= pow; | ||
if (n < pow) return space (n.toFixed (decimals), chars) + arr[i]; | ||
i++; | ||
} | ||
return ">=" + pow + arr[7]; | ||
}; | ||
var zero = function (n){ | ||
return n < 10 ? "0" + n : n; | ||
}; | ||
module.exports.format = { | ||
storage: function (b, decimals){ | ||
return unit (~~b, storage, 1024, decimals === undefined ? 1 : decimals); | ||
}, | ||
speed: function (bps, decimals){ | ||
return unit (~~bps, speeds, 1000, decimals === undefined ? 1 : decimals); | ||
}, | ||
time: function (s){ | ||
if (s === undefined) return "--:--"; | ||
if (s >= 86400000) return " > 1d"; | ||
if (s >= 3600000) return " > 1h"; | ||
var str; | ||
var min = ~~(s/60); | ||
var sec = ~~(s%60); | ||
return zero (min) + ":" + zero (sec); | ||
}, | ||
percentage: function (n){ | ||
return space (Math.round (n*100) + "%", 4); | ||
} | ||
}; | ||
var StatusBar = function (options){ | ||
@@ -16,33 +66,59 @@ if (options.total === undefined || options.total === null){ | ||
} | ||
if (!options.render){ | ||
throw new Error ("Missing rendering function"); | ||
} | ||
stream.Writable.call (this); | ||
this._total = options.total; | ||
this._frequency = options.frequency || null; | ||
var me = this; | ||
this.on ("unpipe", function (){ | ||
me.cancel (); | ||
}); | ||
this._render = options.render; | ||
this._frequency = options.frequency || 200; | ||
this._finish = options.finish; | ||
this._progress = pbf.create ({ | ||
completion: options.barCompletion, | ||
incompletion: options.barIncompletion, | ||
length: options.barLength | ||
complete: options.progressBarComplete, | ||
incomplete: options.progressBarIncomplete, | ||
length: options.progressBarLength | ||
}); | ||
this._current = 0; | ||
this._timer = null; | ||
this._total = ~~options.total; | ||
this._renderTimer = null; | ||
this._elapsedTimer = null; | ||
this._start = 0; | ||
this._chunkTimestamp = 0; | ||
this._smooth = 0.005; | ||
this._secondsWithoutUpdate = 0; | ||
this.stats = { | ||
total: this._total + "" | ||
this._stats = { | ||
currentSize: 0, | ||
totalSize: this._total, | ||
remainingSize: this._total, | ||
speed: 0, | ||
elapsedTime: 0 | ||
}; | ||
this._format (); | ||
if (options.write){ | ||
this._render = options.write; | ||
options.write.call (this); | ||
if (this._frequency && this._total > 0){ | ||
var me = this; | ||
this._timer = setInterval (function (){ | ||
options.write.call (me); | ||
}, this._frequency); | ||
} | ||
var percentage; | ||
if (this._total === 0){ | ||
percentage = 1; | ||
this._stats.remainingTime = 0; | ||
}else{ | ||
percentage = 0; | ||
//Undefined remainingTime | ||
} | ||
this._stats.progressBar = this._progress.format (percentage); | ||
this._stats.percentage = percentage; | ||
//Render for the first time | ||
this._render (this._stats); | ||
if (this._frequency && this._total > 0){ | ||
//If the file has a size of 0 the update function is never called and the | ||
//bar is never rendered again, so there's no need to create a timer | ||
this._renderTimer = setInterval (function (){ | ||
me._render (me._stats); | ||
}, this._frequency); | ||
} | ||
}; | ||
@@ -57,93 +133,55 @@ | ||
var space = function (n){ | ||
n += ""; | ||
while (n.length < 6){ | ||
n = " " + n; | ||
} | ||
return n; | ||
}; | ||
var units = [" B ", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB", | ||
" YiB"]; | ||
var speeds = ["B/s", "K/s", "M/s", "G/s", "T/s", "P/s", "E/s", "Z/s", "Y/s"]; | ||
StatusBar.prototype._unit = function (n, arr, pow){ | ||
if (n < pow) return space (n) + arr[0]; | ||
var i = 1; | ||
while (i < 9){ | ||
n /= pow; | ||
if (n < pow) return space (n.toFixed (1)) + arr[i]; | ||
i++; | ||
} | ||
return ">=" + pow + arr[7]; | ||
}; | ||
StatusBar.prototype._formatSize = function (){ | ||
return this._unit (this._current, units, 1024); | ||
}; | ||
StatusBar.prototype._formatSpeed = function (bytes){ | ||
if (bytes === undefined) return " 0B/s"; | ||
return this._unit (~~bytes, speeds, 1000); | ||
}; | ||
var zero = function (n){ | ||
return n < 10 ? "0" + n : n; | ||
}; | ||
StatusBar.prototype._formatTime = function (t){ | ||
if (t === undefined) return this._current === this._total ? "00:00" : "--:--"; | ||
var str; | ||
if (t >= 86400000) return " > 1d"; | ||
if (t >= 3600000) return " > 1h"; | ||
t /= ~~1000; | ||
var min = ~~(t/60); | ||
var sec = ~~(t%60); | ||
return zero (min) + ":" + zero (sec); | ||
}; | ||
StatusBar.prototype._format = function (length){ | ||
StatusBar.prototype._updateStats = function (length){ | ||
var end = this._current === this._total; | ||
var elapsed; | ||
var now = Date.now (); | ||
var end = this._current === this._total; | ||
var n; | ||
this.stats.current = this._current + ""; | ||
//The elapsed time needs to be calculated with a timer because if it's | ||
//calculated using the elapsed time from the start of the transfer, if the | ||
//transfer is hung up, the stat must continue to be updated each second | ||
if (!this._elapsedTimer){ | ||
var me = this; | ||
this._elapsedTimer = setInterval (function (){ | ||
//Wait 3 seconds before considering a transfer hang up | ||
if (++me._secondsWithoutUpdate === 3){ | ||
me._stats.speed = 0; | ||
me._stats.remainingTime = undefined; | ||
} | ||
me._stats.elapsedTime++; | ||
}, 1000); | ||
} | ||
this.stats.size = this._formatSize (); | ||
this._stats.currentSize = this._current; | ||
this._stats.remainingSize = this._total - this._current; | ||
this._stats.percentage = this._current/this._total; | ||
this._stats.progressBar = this._progress.format (this._stats.percentage); | ||
//The transfer speed is extrapolated from the time between chunks | ||
//The speed and remaining time cannot be calculated only with the first packet | ||
if (this._chunkTimestamp){ | ||
//The transfer speed is extrapolated from the time between chunks | ||
elapsed = process.hrtime (this._chunkTimestamp); | ||
//Elapsed in nanoseconds | ||
elapsed = elapsed[0]*1e9 + elapsed[1]; | ||
if (!end){ | ||
//The last packet slows down the speed | ||
this.stats.speed = this._formatSpeed ((length*1e9)/elapsed); | ||
if (end){ | ||
this._stats.speed = 0; | ||
}else{ | ||
//Bytes per second | ||
var lastSpeed = (length*1e9)/elapsed; | ||
this._stats.speed = ~~(this._smooth*lastSpeed + | ||
(1 - this._smooth)*(this._stats.speed || lastSpeed)); | ||
} | ||
this._chunkTimestamp = process.hrtime (); | ||
}else{ | ||
this.stats.speed = this._formatSpeed (); | ||
if (end){ | ||
this._stats.remainingTime = 0; | ||
}else{ | ||
elapsed = Date.now () - this._start; | ||
this._stats.remainingTime = | ||
~~((0.001*elapsed*this._stats.remainingSize)/this._current) + 1; | ||
} | ||
} | ||
//The estimated remaining time uses the current file size | ||
if (this._start){ | ||
n = end ? 0 : 1000; | ||
elapsed = now - this._start; | ||
var remaining = this._total - this._current; | ||
this.stats.eta = this._formatTime ((elapsed*remaining)/this._current + n); | ||
}else{ | ||
this.stats.eta = this._formatTime (); | ||
} | ||
n = this._total === 0 ? 1 : this._current/this._total; | ||
this.stats.progress = this._progress.format (n); | ||
this.stats.percentage = Math.round (n*100) + "%"; | ||
while (this.stats.percentage.length !== 4){ | ||
this.stats.percentage = " " + this.stats.percentage; | ||
} | ||
}; | ||
StatusBar.prototype.clearInterval = function (){ | ||
clearInterval (this._timer); | ||
StatusBar.prototype.cancel = function (){ | ||
clearInterval (this._renderTimer); | ||
clearInterval (this._elapsedTimer); | ||
}; | ||
@@ -154,22 +192,19 @@ | ||
this._secondsWithoutUpdate = 0; | ||
//Allow any object with a length property | ||
var length = chunk.length || chunk; | ||
this._current += length; | ||
this._format (length); | ||
this._updateStats (length); | ||
if (!this._chunkTimestamp){ | ||
this._chunkTimestamp = process.hrtime (); | ||
} | ||
//High resolution timer between packets | ||
this._chunkTimestamp = process.hrtime (); | ||
if (!this._render) return; | ||
//Force a writing if there's no timer | ||
if (!this._timer) return this._render (); | ||
//Force a writing if the progress has finished and it has a timer | ||
//Force a render if the transfer has finished | ||
if (this._current === this._total){ | ||
this.clearInterval (); | ||
this._render (); | ||
this.cancel (); | ||
this._render (this._stats); | ||
if (this._finish) this._finish (); | ||
} | ||
}; |
{ | ||
"name": "status-bar", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "A status bar for file transfers", | ||
@@ -5,0 +5,0 @@ "keywords": ["status", "bar", "file", "transfer", "speed", "progress", |
207
README.md
@@ -19,10 +19,11 @@ status-bar | ||
total: size, | ||
//Writing frequency | ||
frequency: 200, | ||
//Writing function | ||
write: function (){ | ||
//Render function | ||
render: function (stats){ | ||
//Print the status bar as you like | ||
process.stdout.write (filename + " " + this.stats.size + " " + | ||
this.stats.speed + " " + this.stats.eta + " [" + this.stats.progress + | ||
"] " + this.stats.percentage); | ||
process.stdout.write (filename + " " + | ||
statusBar.format.storage (stats.currentSize) + " " + | ||
statusBar.format.speed (stats.speed) + " " + | ||
statusBar.format.time (stats.remainingTime) + " [" + | ||
stats.progressBar + "] " + | ||
statusBar.format.percentage (stats.percentage)); | ||
process.stdout.cursorTo (0); | ||
@@ -33,3 +34,3 @@ } | ||
//Update the status bar when you send or receive a chunk of a file | ||
.on ("some-event", function (chunk){ | ||
obj.on ("some-event", function (chunk){ | ||
//You can pass any object that contains a length property or a simple number | ||
@@ -48,4 +49,5 @@ bar.update (chunk); | ||
- It doesn't print anything, it just formats the data and you decide how you want to print the status bar. Other modules similar to this use the `readline` module which is very unstable and may cause problems if you are already using a `readline` instance. | ||
- You decide how to arrange the elements of the status bar. Because each element has a fixed length you can format the status bar very easily. | ||
- It doesn't print anything, it just calculates and returns raw data and provides default formatting functions. Other modules similar to this force you to use their own formatting functions with the `readline` module, which is very unstable and may cause problems if you are already using a `readline` instance. | ||
- The status bar can be displayed wherever you want, it is simply a string, so you can render it in the console, in HTML (probably with your own progress bar) sending it via websockets or with [node-webkit](https://github.com/rogerwang/node-webkit), etc. | ||
- You decide how format and arrange the elements of the status bar. The default formatting functions have a fixed length, so you can format the status bar very easily. | ||
- It is very easy to use. Just `pipe()` things to it! | ||
@@ -62,2 +64,4 @@ | ||
```javascript | ||
var statusBar = require ("status-bar"); | ||
var formatFilename = function (filename){ | ||
@@ -79,8 +83,16 @@ //80 - 59 | ||
var write = function (){ | ||
process.stdout.write (filename + " " + this.stats.size + " " + | ||
this.stats.speed + " " + this.stats.eta + " [" + | ||
this.stats.progress + "] " + this.stats.percentage); | ||
var render = function (stats){ | ||
process.stdout.write (filename + " " + | ||
statusBar.format.storage (stats.currentSize) + " " + | ||
statusBar.format.speed (stats.speed) + " " + | ||
statusBar.format.time (stats.remainingTime) + " [" + | ||
stats.progressBar + "] " + | ||
statusBar.format.percentage (stats.percentage)); | ||
process.stdout.cursorTo (0); | ||
}; | ||
var bar = statusBar.create ({ | ||
total: ..., | ||
render: render | ||
}); | ||
``` | ||
@@ -95,8 +107,18 @@ | ||
```javascript | ||
var write = function (){ | ||
process.stdout.write ("Receiving objects: " + this.stats.percentage.trim () + | ||
" (" + this.stats.current + "/" + this.stats.total + "), " + | ||
this.stats.size.trim () + " | " + this.stats.speed.trim ()); | ||
var statusBar = require ("status-bar"); | ||
var render = function (stats){ | ||
process.stdout.write ("Receiving objects: " + | ||
statusBar.format.percentage (stats.percentage).trim () + | ||
" (" + stats.currentSize + "/" + stats.totalSize + "), " + | ||
statusBar.format.storage (stats.currentSize).trim () + " | " + | ||
statusBar.format.speed (stats.speed).trim ()); | ||
process.stdout.cursorTo (0); | ||
}; | ||
var bar = statusBar.create ({ | ||
total: ..., | ||
render: render | ||
}); | ||
``` | ||
@@ -107,2 +129,6 @@ | ||
- [_module_.create(options) : StatusBar](#create) | ||
- [_module_.format.percentage(percentage) : String](#format-percentage) | ||
- [_module_.format.speed(bytesPerSecond) : String](#format-speed) | ||
- [_module_.format.storage(bytes) : String](#format-storage) | ||
- [_module_.format.time(seconds) : String](#format-time) | ||
@@ -122,94 +148,111 @@ #### Objects #### | ||
- __total__ - _Number_ | ||
The total size of a file. This option is required. | ||
- __barComplete__ - _String_ | ||
- __finish__ - _Function_ | ||
Function that is called when the file transfer has finished. | ||
- __frequency__ - _Number_ | ||
The rendering frequency in milliseconds. It must be a positive value. Default is 200. | ||
- __progressBarComplete__ - _String_ | ||
The character that shows completion progress. Default is `#`. | ||
- __barIncomplete__ - _String_ | ||
- __progressBarIncomplete__ - _String_ | ||
The character that shows the remaining progress. Default is `·`. | ||
- __barLength__ - _Number_ | ||
The length of the progress bar. Default is `24`. | ||
- __frequency__ - _Number_ | ||
The writing frequency. If you don't configure a `write` function, this option is ignored. By default there's no value, so each time you call to [update()](#statusbar_update), the status bar is printed. This is the most accurate behaviour but it slows down the file transfer very much. I recommend to render the status bar every 200ms, remember that a status bar is purely informational. | ||
- __write__ - _Function_ | ||
Function that is called when the status bar needs to be printed. | ||
- __finish__ - _Function_ | ||
Function that is called when the file transfer has finished. | ||
- __progressBarLength__ - _Number_ | ||
The length of the progress bar. Default is 24. | ||
- __render__ - _Function_ | ||
Function that is called when the status bar needs to be printed. It is required. It receives the stats object as an argument. All of its properties contain raw data (except the progress bar), so you need to format them. You can use the default formatting functions. | ||
Properties: | ||
- __currentSize__ - _Number_ | ||
The current size in bytes. | ||
- __remainingSize__ - _Number_ | ||
The remaining size in bytes. | ||
- __totalSize__ - _Number_ | ||
The total size in bytes. | ||
- __percentage__ - _Number_ | ||
The complete percentage. A number between 0 and 1. | ||
- __speed__ - _Number_ | ||
The estimated current speed in bytes per second. | ||
- __elapsedTime__ - _Number_ | ||
The elapsed time in seconds. | ||
- __remainingTime__ - _Number_ | ||
The estimated remaining time in seconds. If the remaining time cannot be estimated because the status bar needs at least 2 chunks or because the transfer it's hung up, it returns `undefined`. | ||
- __progressBar__ - _String_ | ||
The progress bar. | ||
``` | ||
######·················· | ||
``` | ||
- __total__ - _Number_ | ||
The total size of the file. This option is required. | ||
--- | ||
<a name="statusbar_object"></a> | ||
__StatusBar__ | ||
<a name="format-percentage"></a> | ||
___module_.format.percentage(percentage) : String__ | ||
__Methods__ | ||
The percentage must be a number between 0 and 1. Result string length: 4. | ||
- [StatusBar#clearInterval() : undefined](#statusbar_clearinterval) | ||
- [StatusBar#update(chunk) : undefined](#statusbar_update) | ||
```javascript | ||
console.log (statusBar.format.percentage (0.5)); | ||
// 50% | ||
``` | ||
__Properties__ | ||
--- | ||
- [StatusBar#stats](#statusbar_stats) | ||
<a name="format-speed"></a> | ||
___module_.format.speed(bytesPerSecond) : String__ | ||
Speed in bytes per second. Result string length: 9. | ||
```javascript | ||
console.log (statusBar.format.speed (30098226)); | ||
// 30.1M/s | ||
``` | ||
--- | ||
<a name="statusbar_clearinterval"></a> | ||
__StatusBar#clearInterval() : undefined__ | ||
<a name="format-storage"></a> | ||
___module_.format.storage(bytes) : String__ | ||
When you need to cancel the status bar rendering because the file transfer has been aborted due to an error or any other reason, call to this function to clear the timer. This is only needed when the `frequency` option is configured. | ||
Result string length: 10. | ||
```javascript | ||
console.log (statusBar.format.storage (38546744)); | ||
// 36.8 MiB | ||
``` | ||
--- | ||
<a name="statusbar_update"></a> | ||
__StatusBar#update(chunk) : undefined__ | ||
<a name="format-time"></a> | ||
___module_.format.time(seconds) : String__ | ||
Updates the status bar. The `chunk` can be any object with a length property or a simple number. | ||
Result string length: 5 (_min_:_sec_). If `seconds` is undefined it prints `--:--`. | ||
```javascript | ||
console.log (statusBar.format.time (63)); | ||
//01:03 | ||
``` | ||
--- | ||
<a name="statusbar_stats"></a> | ||
__StatusBar#stats__ | ||
<a name="statusbar_object"></a> | ||
__StatusBar__ | ||
`stats` is an object that contains the current state of the status bar. It is updated each time you [update()](statusbar_update) the status bar. All the following properties are strings and most of them have a fixed length. | ||
__Methods__ | ||
- __current__ - _String_ | ||
The current file size. Length: variable. Example: | ||
- [StatusBar#cancel() : undefined](#statusbar_cancel) | ||
- [StatusBar#update(chunk) : undefined](#statusbar_update) | ||
``` | ||
1234 | ||
``` | ||
- __eta__ - _String_ | ||
The estimated remaining time. Length: 5. Example (_min_:_sec_): | ||
--- | ||
``` | ||
01:45 | ||
``` | ||
- __percentage__ - _String_ | ||
The completion percentage. Length: 4. Example: | ||
<a name="statusbar_cancel"></a> | ||
__StatusBar#cancel() : undefined__ | ||
``` | ||
100% | ||
``` | ||
- __progress__ - _String_ | ||
A progress bar with the current file completion. Length: configured with the `barLength` option. Example: | ||
When you need to cancel the status bar rendering because the file transfer has been aborted due to an error or any other reason, call to this function to clear the timer. This is only needed when the `frequency` option is configured. | ||
``` | ||
##########·············· | ||
``` | ||
- __size__ - _String_ | ||
The current formatted size of the file that is being received/sent. Length: 10. Example: | ||
--- | ||
``` | ||
12.5 MiB | ||
``` | ||
- __speed__ - _String_ | ||
The current file transfer speed. Length: 9. Example: | ||
<a name="statusbar_update"></a> | ||
__StatusBar#update(chunk) : undefined__ | ||
``` | ||
5.3M/s | ||
``` | ||
- __total__ - _String_ | ||
The total file size. Length: variable. Example: | ||
``` | ||
5678 | ||
``` | ||
Updates the status bar. The `chunk` can be any object with a length property or a simple number. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
15077
175
1
250
1