ipull
Version:
The only file downloader you'll ever need. For node.js and the browser, CLI and library for fast and reliable file downloads.
300 lines • 10.8 kB
JavaScript
import chalk from "chalk";
import { truncateText } from "../../utils/cli-text.js";
import { clamp } from "../../utils/numbers.js";
import { DownloadStatus } from "../../../download-engine/download-file/progress-status-file.js";
import { BaseMultiProgressBar } from "../multiProgressBars/BaseMultiProgressBar.js";
import { STATUS_ICONS } from "../../utils/progressBarIcons.js";
import { renderDataLine } from "../../utils/data-line.js";
import cliSpinners from "cli-spinners";
const SKIP_ETA_START_TIME = 1000 * 2;
const MIN_NAME_LENGTH = 20;
const MIN_COMMENT_LENGTH = 15;
const DEFAULT_SPINNER_UPDATE_INTERVAL_MS = 10;
/**
* A class to display transfer progress in the terminal, with a progress bar and other information.
*/
export default class BaseTransferCliProgressBar {
multiProgressBar = BaseMultiProgressBar;
downloadLoadingSpinner;
_spinnerState = {
step: 0,
lastChanged: 0
};
status = null;
options;
minNameLength = MIN_NAME_LENGTH;
constructor(options) {
this.options = options;
this.downloadLoadingSpinner = cliSpinners[options.loadingSpinner ?? "dots"];
}
switchTransferToShortText() {
switch (this.status.transferAction) {
case "Downloading":
return "Pull";
case "Copying":
return "Copy";
}
return this.status.transferAction;
}
get showETA() {
return this.status.startTime < Date.now() - SKIP_ETA_START_TIME;
}
get alertStatus() {
if (this.status.retrying) {
return `(retrying #${this.status.retryingTotalAttempts})`;
}
else if (this.status.streamsNotResponding) {
return `(${this.status.streamsNotResponding} streams not responding)`;
}
return "";
}
getNameSize(fileName = this.status.fileName) {
return this.options.truncateName === false
? fileName.length
: typeof this.options.truncateName === "number"
? this.options.truncateName
: Math.min(fileName.length, this.minNameLength);
}
getSpinnerText() {
const spinner = this.downloadLoadingSpinner.frames[this._spinnerState.step];
if (this._spinnerState.lastChanged + DEFAULT_SPINNER_UPDATE_INTERVAL_MS < Date.now()) {
this._spinnerState.step++;
if (this._spinnerState.step >= this.downloadLoadingSpinner.frames.length) {
this._spinnerState.step = 0;
}
this._spinnerState.lastChanged = Date.now();
}
return spinner;
}
getNameAndCommentDataParts() {
const { fileName, comment, downloadStatus } = this.status;
let fullComment = comment;
if (downloadStatus === DownloadStatus.Cancelled || downloadStatus === DownloadStatus.Paused) {
if (fullComment) {
fullComment += " | " + downloadStatus;
}
else {
fullComment = downloadStatus;
}
}
return [{
type: "name",
fullText: fileName,
size: this.getNameSize(),
flex: typeof this.options.truncateName === "number"
? undefined
: 1,
maxSize: fileName.length,
cropper: truncateText,
formatter: (text) => chalk.bold(text)
}, ...((fullComment == null || fullComment.length === 0)
? []
: [{
type: "spacer",
fullText: " (",
size: " (".length,
formatter: (text) => chalk.dim(text)
}, {
type: "nameComment",
fullText: fullComment,
size: Math.min(fullComment.length, MIN_COMMENT_LENGTH),
maxSize: fullComment.length,
flex: 1,
cropper: truncateText,
formatter: (text) => chalk.dim(text)
}, {
type: "spacer",
fullText: ")",
size: ")".length,
formatter: (text) => chalk.dim(text)
}])];
}
getETA(spacer = " | ", formatter = text => text) {
const formatedTimeLeft = this.status.timeLeft < 1_000 ? "0s" : this.status.formatTimeLeft;
const timeLeft = `${formatedTimeLeft.padStart("10s".length)} left`;
if (this.showETA) {
return [{
type: "spacer",
fullText: spacer,
size: spacer.length,
formatter(text, size) {
return formatter(text, size, "spacer");
}
}, {
type: "timeLeft",
fullText: timeLeft,
size: timeLeft.length,
formatter(text, size) {
return formatter(text, size, "time");
}
}];
}
return [];
}
createProgressBarLine(length) {
const fileName = truncateText(this.status.fileName, length);
const percentage = clamp(this.status.transferredBytes / this.status.totalBytes, 0, 1);
const fullLength = Math.floor(percentage * length);
const emptyLength = length - fullLength;
return chalk.cyan(fileName.slice(0, fullLength)) + chalk.dim(fileName.slice(fullLength, fullLength + emptyLength));
}
renderProgressLine() {
const { formattedPercentage, formattedSpeed, formatTransferredOfTotal, formatTotal } = this.status;
const status = this.switchTransferToShortText();
const alertStatus = this.alertStatus;
return renderDataLine([
{
type: "status",
fullText: status,
size: status.length,
formatter: (text) => chalk.cyan(text)
},
{
type: "status",
fullText: alertStatus,
size: alertStatus.length,
formatter: (text) => chalk.ansi256(196)(text)
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
{
type: "percentage",
fullText: formattedPercentage,
size: "100.00%".length,
formatter: () => chalk.green(formattedPercentage)
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
{
type: "progressBar",
size: this.getNameSize(),
fullText: "",
flex: 4,
addEndPadding: 4,
maxSize: 40,
formatter: (_, size) => {
return `[${this.createProgressBarLine(size)}]`;
}
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
{
type: "transferred",
fullText: formatTransferredOfTotal,
size: `999.99MB/${formatTotal}`.length
},
{
type: "spacer",
fullText: " (",
size: " (".length
},
{
type: "speed",
fullText: formattedSpeed,
size: Math.max("00.00kB/s".length, formattedSpeed.length),
formatter: text => chalk.ansi256(31)(text)
},
{
type: "spacer",
fullText: ")",
size: ")".length
},
...this.getETA(" ~ ", text => chalk.dim(text))
]);
}
renderFinishedLine() {
const status = this.status.downloadStatus === DownloadStatus.Finished ? chalk.green(STATUS_ICONS.done) : chalk.red(STATUS_ICONS.failed);
return renderDataLine([
{
type: "status",
fullText: "",
size: 1,
formatter: () => status
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
...this.getNameAndCommentDataParts()
]);
}
renderPendingLine() {
return renderDataLine([
{
type: "status",
fullText: "",
size: 1,
formatter: () => STATUS_ICONS.pending
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
...this.getNameAndCommentDataParts(),
{
type: "spacer",
fullText: " ",
size: " ".length
},
{
type: "description",
fullText: this.status.formatTotal,
size: this.status.formatTotal.length,
formatter: (text) => chalk.dim(text)
}
]);
}
renderLoadingLine() {
const spinner = this.getSpinnerText();
const showText = "Gathering information";
return renderDataLine([
{
type: "status",
fullText: spinner,
size: spinner.length,
formatter: (text) => chalk.cyan(text)
},
{
type: "spacer",
fullText: " ",
size: " ".length
},
{
type: "name",
fullText: showText,
size: this.getNameSize(showText),
flex: typeof this.options.truncateName === "number"
? undefined
: 1,
maxSize: showText.length,
cropper: truncateText,
formatter: (text) => chalk.bold(text)
}
]);
}
createStatusLine(status) {
this.status = status;
if ([DownloadStatus.Finished, DownloadStatus.Error, DownloadStatus.Cancelled].includes(this.status.downloadStatus)) {
return this.renderFinishedLine();
}
if (this.status.downloadStatus === DownloadStatus.NotStarted) {
return this.renderPendingLine();
}
if (this.status.downloadStatus === DownloadStatus.Loading) {
return this.renderLoadingLine();
}
return this.renderProgressLine();
}
}
//# sourceMappingURL=base-transfer-cli-progress-bar.js.map