- Add FIFO semaphore for per-hoster concurrency control - Add token-bucket speed limiter with abort signal support - Rewrite upload-manager with retry loop, speed monitoring, and rich progress events - Add per-hoster settings: retries, max speed, parallel count, restart below speed, time interval, max size - Add context menu with shutdown-after-finish (sleep/shutdown/restart), always-on-top - Add z-o-o-m-style queue table with 8 columns, status-colored rows, progress bars - Add debounced queue rendering with scroll position preservation - Add statusbar with global speed, total bytes, elapsed time - Fix speedMonitor interval leak on error and scoping bug - Fix throttle not respecting abort signal during cancellation - Fix combined signal listener cleanup - Bump version to 1.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
43 lines
1.0 KiB
JavaScript
43 lines
1.0 KiB
JavaScript
/**
|
|
* Token-bucket speed limiter for bandwidth throttling.
|
|
* maxBytesPerSec = 0 means unlimited (passthrough).
|
|
*/
|
|
class Throttle {
|
|
constructor(maxBytesPerSec) {
|
|
this.maxBps = maxBytesPerSec || 0;
|
|
this.tokens = this.maxBps;
|
|
this.lastRefill = Date.now();
|
|
}
|
|
|
|
async consume(bytes, signal) {
|
|
if (this.maxBps <= 0) return; // unlimited
|
|
|
|
while (bytes > 0) {
|
|
if (signal && signal.aborted) return;
|
|
this._refill();
|
|
const available = Math.min(bytes, Math.floor(this.tokens));
|
|
if (available > 0) {
|
|
this.tokens -= available;
|
|
bytes -= available;
|
|
}
|
|
if (bytes > 0) {
|
|
// Wait 50ms for tokens to refill
|
|
await new Promise((r) => setTimeout(r, 50));
|
|
}
|
|
}
|
|
}
|
|
|
|
_refill() {
|
|
const now = Date.now();
|
|
const elapsed = (now - this.lastRefill) / 1000;
|
|
this.tokens = Math.min(this.maxBps, this.tokens + elapsed * this.maxBps);
|
|
this.lastRefill = now;
|
|
}
|
|
|
|
updateRate(maxBytesPerSec) {
|
|
this.maxBps = maxBytesPerSec || 0;
|
|
}
|
|
}
|
|
|
|
module.exports = Throttle;
|