@vbarbarosh/node-helpers 3.34.2 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
3
3
  "license": "MIT",
4
4
  "name": "@vbarbarosh/node-helpers",
5
- "version": "3.34.2",
5
+ "version": "3.36.0",
6
6
  "description": "A set of helpers for JavaScript/Node.js",
7
7
  "files": [
8
8
  "src"
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Wait for a promise/value to be settled. Meanwhile, call `tick` function every `tick_ms` milliseconds.
3
+ * After `timeout` seconds, reject with 'Timeout' error.
4
+ *
5
+ * @see user_friendly_status
6
+ * @see progress
7
+ */
8
+ function countdown(ctx)
9
+ {
10
+ let done = false;
11
+ let timer = null;
12
+ return new Promise(function (resolve, reject) {
13
+ ctx.value = Promise.resolve(ctx.value);
14
+ ctx.timeout = ctx.timeout || 0;
15
+ ctx.time_now = new Date();
16
+ ctx.time_begin = new Date();
17
+ ctx.time_end = Date.now() + ctx.timeout;
18
+ ctx.tick_ms = ctx.tick_ms || 1000;
19
+ ctx.tick = ctx.tick || function () {};
20
+ ctx.resolve = function (value) {
21
+ if (done) {
22
+ throw new Error('Already settled');
23
+ }
24
+ done = true;
25
+ clearInterval(timer);
26
+ resolve(value);
27
+ };
28
+ ctx.reject = function (error) {
29
+ if (done) {
30
+ throw new Error('Already settled');
31
+ }
32
+ done = true;
33
+ clearInterval(timer);
34
+ reject(error);
35
+ };
36
+ timer = setInterval(tick, ctx.tick_ms);
37
+ ctx.value.then(ctx.resolve, ctx.reject);
38
+ tick();
39
+ function tick() {
40
+ ctx.time_now = Date.now();
41
+ ctx.time_elapsed = ctx.time_now - ctx.time_begin;
42
+ ctx.time_remained = Math.max(0, ctx.time_end - ctx.time_now);
43
+ ctx.tick(ctx);
44
+ if (!ctx.time_remained) {
45
+ ctx.reject(new Error('Timeout'));
46
+ }
47
+ }
48
+ });
49
+ }
50
+
51
+ module.exports = countdown;
@@ -0,0 +1,35 @@
1
+ const Promise = require('bluebird');
2
+ const child_process = require('child_process');
3
+ const stream = require('stream');
4
+ const stream_curl_progress = require('./stream_curl_progress');
5
+ const stream_each = require('./stream_each');
6
+
7
+ async function shell_curl_progress(args, {options, user_friendly_status})
8
+ {
9
+ // $ man curl
10
+ // > If you want a progress meter for HTTP POST or PUT requests,
11
+ // > you need to redirect the response output to a file, using
12
+ // > shell redirect (>), -o, --output or similar.
13
+
14
+ const tmp = ['--progress-meter'];
15
+ if (!args.includes('-o')) {
16
+ tmp.push('-o', '/dev/null');
17
+ }
18
+ const proc = child_process.spawn(args[0], args.slice(1).concat(tmp), {...options, stdio: ['inherit', 'inherit', 'pipe']});
19
+
20
+ const promises = [];
21
+ promises.push(new Promise(function (resolve, reject) {
22
+ proc.once('error', reject);
23
+ proc.once('exit', code => code ? reject(new Error(`Process terminated with code ${code}`)) : resolve());
24
+ }));
25
+ promises.push(stream.promises.pipeline(proc.stderr, stream_curl_progress(), stream_each(progress_fn)));
26
+
27
+ await Promise.all(promises);
28
+
29
+ function progress_fn(v) {
30
+ const speed = v.speed === '0' ? '~' : `${v.speed}/s`;
31
+ user_friendly_status(`${v.perc}% | ${v.done} of ${v.total} at ${speed} ETA ${v.eta} duration=${v.duration}`);
32
+ }
33
+ }
34
+
35
+ module.exports = shell_curl_progress;
@@ -0,0 +1,21 @@
1
+ const child_process = require('child_process');
2
+ const stream = require('stream');
3
+ const stream_each = require('./stream_each');
4
+ const stream_ytdlp_progress = require('./stream_ytdlp_progress');
5
+
6
+ async function shell_ytdlp_progress(args, {progress_fn, ...options})
7
+ {
8
+ const proc = child_process.spawn('yt-dlp', ['--progress', ...args], {...options, stdio: 'pipe'});
9
+
10
+ const promises = [];
11
+ promises.push(new Promise(function (resolve, reject) {
12
+ proc.once('error', reject);
13
+ proc.once('exit', code => code ? reject(new Error(`Process terminated with code ${code}`)) : resolve());
14
+ }));
15
+ promises.push(stream.promises.pipeline(proc.stdout, stream_ytdlp_progress(), stream_each(progress_fn)));
16
+ promises.push(stream.promises.pipeline(proc.stderr, stream_each(v => console.log(`[stderr] ${v}`))));
17
+
18
+ await Promise.all(promises);
19
+ }
20
+
21
+ module.exports = shell_ytdlp_progress;
@@ -0,0 +1,46 @@
1
+ const stream = require('stream');
2
+ const stream_filter = require('./stream_filter');
3
+ const stream_lines = require('./stream_lines');
4
+ const stream_map = require('./stream_map');
5
+ const stream_skip = require('./stream_skip');
6
+
7
+ function stream_curl_progress()
8
+ {
9
+ return stream.compose(
10
+ stream_lines(),
11
+ stream_skip(2),
12
+ stream_map(function (v) {
13
+ // curl: (22) The requested URL returned error: 500
14
+ if (v.startsWith('curl:')) {
15
+ process.stderr.write(`${v}\n`);
16
+ return null;
17
+ }
18
+
19
+ // % Total % Received % Xferd Average Speed Time Time Time Current
20
+ // Dload Upload Total Spent Left Speed
21
+ // 0 3217M 0 0 0 128k 0 205k 4:27:16 --:--:-- 4:27:16 205k
22
+ // 0 3217M 0 0 0 6016k 0 3751k 0:14:38 0:00:01 0:14:37 3750k
23
+ // ...
24
+ // 18 3217M 0 0 18 587M 0 18.5M 0:02:53 0:00:31 0:02:22 19.0M
25
+ const [perc, total, perc2, recv, perc3, transferred, speed_down, speed_up, time_total, duration, eta, speed] = v.trim().split(/\s+/);
26
+ return {
27
+ perc,
28
+ done: recv === '0' ? transferred : recv,
29
+ total,
30
+ perc2,
31
+ recv,
32
+ perc3,
33
+ transferred,
34
+ speed_down,
35
+ speed_up,
36
+ time_total: time_total === '--:--:--' ? '~' : time_total,
37
+ duration: duration === '--:--:--' ? '~' : duration,
38
+ eta: eta === '--:--:--' ? '~' : eta,
39
+ speed
40
+ };
41
+ }),
42
+ stream_filter(v => v),
43
+ );
44
+ }
45
+
46
+ module.exports = stream_curl_progress;
@@ -0,0 +1,25 @@
1
+ const stream = require('stream');
2
+
3
+ /**
4
+ * Skip first `n` records.
5
+ */
6
+ function stream_skip(n)
7
+ {
8
+ let current = 0;
9
+ return new stream.Transform({
10
+ objectMode: true,
11
+ transform: function (item, encoding, callback) {
12
+ try {
13
+ if (current++ >= n) {
14
+ this.push(item);
15
+ }
16
+ callback();
17
+ }
18
+ catch (error) {
19
+ callback(error);
20
+ }
21
+ },
22
+ });
23
+ }
24
+
25
+ module.exports = stream_skip;
@@ -0,0 +1,19 @@
1
+ const stream = require('stream');
2
+ const stream_filter = require('./stream_filter');
3
+ const stream_lines = require('./stream_lines');
4
+ const stream_map = require('./stream_map');
5
+
6
+ function stream_ytdlp_progress()
7
+ {
8
+ const re = /^\[download] (.*) of (.*) at (.*) ETA (.*)$/;
9
+ return stream.compose(
10
+ stream_lines(),
11
+ stream_map(function (v) {
12
+ const m = re.exec(v);
13
+ return !m ? false : {done: m[1].trim(), of: m[2].trim(), speed: m[3].trim(), eta: m[4].trim()};
14
+ }),
15
+ stream_filter(v => v),
16
+ );
17
+ }
18
+
19
+ module.exports = stream_ytdlp_progress;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Concept. Represents a way to display a status of a currently running task to the end-user.
3
+ *
4
+ * @see countdown
5
+ * @see progress
6
+ */
7
+ function user_friendly_status(s)
8
+ {
9
+ console.log(s);
10
+ }
11
+
12
+ module.exports = user_friendly_status;