1019 lines
27 KiB
C
1019 lines
27 KiB
C
/*
|
|
* Display functions.
|
|
*/
|
|
|
|
#include "pv-internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
|
|
/*
|
|
* Output an error message. If we've displayed anything to the terminal
|
|
* already, then put a newline before our error so we don't write over what
|
|
* we've written.
|
|
*/
|
|
void pv_error(pvstate_t state, char *format, ...)
|
|
{
|
|
va_list ap;
|
|
if (state->display_visible)
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "%s: ", state->program_name);
|
|
va_start(ap, format);
|
|
(void) vfprintf(stderr, format, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* Return true if we are the foreground process on the terminal, or if we
|
|
* aren't outputting to a terminal; false otherwise.
|
|
*/
|
|
bool pv_in_foreground(void)
|
|
{
|
|
pid_t our_process_group;
|
|
pid_t tty_process_group;
|
|
|
|
if (0 == isatty(STDERR_FILENO))
|
|
return true;
|
|
|
|
our_process_group = getpgrp();
|
|
tty_process_group = tcgetpgrp(STDERR_FILENO);
|
|
|
|
if (tty_process_group == -1 && errno == ENOTTY)
|
|
return true;
|
|
|
|
if (our_process_group == tty_process_group)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Fill in *width and *height with the current terminal size,
|
|
* if possible.
|
|
*/
|
|
void pv_screensize(unsigned int *width, unsigned int *height)
|
|
{
|
|
#ifdef TIOCGWINSZ
|
|
struct winsize wsz;
|
|
|
|
if (0 != isatty(STDERR_FILENO)) {
|
|
if (0 == ioctl(STDERR_FILENO, TIOCGWINSZ, &wsz)) {
|
|
*width = wsz.ws_col;
|
|
*height = wsz.ws_row;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate the percentage transferred so far and return it.
|
|
*/
|
|
static long pv__calc_percentage(long long so_far, const long long total)
|
|
{
|
|
if (total < 1)
|
|
return 0;
|
|
|
|
so_far *= 100;
|
|
so_far /= total;
|
|
|
|
return (long) so_far;
|
|
}
|
|
|
|
|
|
/*
|
|
* Given how many bytes have been transferred, the total byte count to
|
|
* transfer, and how long it's taken so far in seconds, return the estimated
|
|
* number of seconds until completion.
|
|
*/
|
|
static long pv__calc_eta(const long long so_far, const long long total,
|
|
const long rate)
|
|
{
|
|
long long amount_left;
|
|
|
|
if (so_far < 1 || !rate)
|
|
return 0;
|
|
|
|
amount_left = (total - so_far) / rate;
|
|
|
|
return (long) amount_left;
|
|
}
|
|
|
|
/*
|
|
* Given a long double value, it is divided or multiplied by the ratio until
|
|
* a value in the range 1.0 to 999.999... is found. The string "prefix" to
|
|
* is updated to the corresponding SI prefix.
|
|
*
|
|
* If "is_bytes" is 1, then the second byte of "prefix" is set to "i" to
|
|
* denote MiB etc (IEEE1541). Thus "prefix" should be at least 3 bytes long
|
|
* (to include the terminating null).
|
|
*
|
|
* Submitted by Henry Gebhardt <hsggebhardt@googlemail.com> and then
|
|
* modified. Further changed after input from Thomas Rachel; changed still
|
|
* further after Debian bug #706175.
|
|
*/
|
|
static void pv__si_prefix(long double *value, char *prefix,
|
|
const long double ratio, int is_bytes)
|
|
{
|
|
static char *pfx_000 = NULL; /* kilo, mega, etc */
|
|
static char *pfx_024 = NULL; /* kibi, mibi, etc */
|
|
static char const *pfx_middle_000 = NULL;
|
|
static char const *pfx_middle_024 = NULL;
|
|
char *pfx;
|
|
char const *pfx_middle;
|
|
char const *i;
|
|
long double cutoff;
|
|
|
|
if (NULL == pfx_000) {
|
|
pfx_000 = _("yzafpnum kMGTPEZY");
|
|
pfx_middle_000 = strchr(pfx_000, ' ');
|
|
}
|
|
|
|
if (NULL == pfx_024) {
|
|
pfx_024 = _("yzafpnum KMGTPEZY");
|
|
pfx_middle_024 = strchr(pfx_024, ' ');
|
|
}
|
|
|
|
pfx = pfx_000;
|
|
pfx_middle = pfx_middle_000;
|
|
if (is_bytes) {
|
|
pfx = pfx_024;
|
|
pfx_middle = pfx_middle_024;
|
|
}
|
|
|
|
i = pfx_middle;
|
|
|
|
prefix[0] = ' '; /* Make the prefix start blank. */
|
|
prefix[1] = 0;
|
|
|
|
/*
|
|
* Force an empty prefix if the value is zero to avoid "0yB".
|
|
*/
|
|
if (0.0 == *value)
|
|
return;
|
|
|
|
cutoff = ratio * 0.97;
|
|
|
|
while ((*value > cutoff) && (*(i += 1) != '\0')) {
|
|
*value /= ratio;
|
|
prefix[0] = *i;
|
|
}
|
|
|
|
while ((*value < 1.0) && ((i -= 1) != (pfx - 1))) {
|
|
*value *= ratio;
|
|
prefix[0] = *i;
|
|
}
|
|
|
|
if (is_bytes && prefix[0] != ' ') {
|
|
prefix[1] = 'i';
|
|
prefix[2] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Put a string in "buffer" (max length "bufsize") containing "amount"
|
|
* formatted such that it's 3 or 4 digits followed by an SI suffix and then
|
|
* whichever of "suffix_basic" or "suffix_bytes" is appropriate (whether
|
|
* "is_bytes" is 0 for non-byte amounts or 1 for byte amounts). If
|
|
* "is_bytes" is 1 then the SI units are KiB, MiB etc and the divisor is
|
|
* 1024 instead of 1000.
|
|
*
|
|
* The "format" string is in sprintf format and must contain exactly one %
|
|
* parameter (a %s) which will expand to the string described above.
|
|
*/
|
|
static void pv__sizestr(char *buffer, int bufsize, char *format,
|
|
long double amount, char *suffix_basic,
|
|
char *suffix_bytes, int is_bytes)
|
|
{
|
|
char sizestr_buffer[256];
|
|
char si_prefix[8] = " ";
|
|
long double divider;
|
|
long double display_amount;
|
|
char *suffix;
|
|
|
|
if (is_bytes) {
|
|
suffix = suffix_bytes;
|
|
divider = 1024.0;
|
|
} else {
|
|
suffix = suffix_basic;
|
|
divider = 1000.0;
|
|
}
|
|
|
|
display_amount = amount;
|
|
|
|
pv__si_prefix(&display_amount, si_prefix, divider, is_bytes);
|
|
|
|
/* Make sure we don't overrun our buffer. */
|
|
if (display_amount > 100000)
|
|
display_amount = 100000;
|
|
|
|
/* Fix for display of "1.01e+03" instead of "1010" */
|
|
if (display_amount > 99.9) {
|
|
sprintf(sizestr_buffer, "%4ld%.2s%.16s",
|
|
(long) display_amount, si_prefix, suffix);
|
|
} else {
|
|
/*
|
|
* AIX blows up with %4.3Lg%.2s%.16s for some reason, so we
|
|
* write display_amount separately first.
|
|
*/
|
|
char str_disp[64];
|
|
/* # to get 13.0GB instead of 13GB (#1477) */
|
|
sprintf(str_disp, "%#4.3Lg", display_amount);
|
|
sprintf(sizestr_buffer, "%s%.2s%.16s",
|
|
str_disp, si_prefix, suffix);
|
|
}
|
|
|
|
#ifdef HAVE_SNPRINTF
|
|
snprintf(buffer, bufsize, format, sizestr_buffer);
|
|
#else
|
|
sprintf(buffer, format, sizestr_buffer);
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialise the output format structure, based on the current options.
|
|
*/
|
|
static void pv__format_init(pvstate_t state)
|
|
{
|
|
const char *formatstr;
|
|
const char *searchptr;
|
|
int strpos;
|
|
int segment;
|
|
|
|
if (NULL == state)
|
|
return;
|
|
|
|
state->str_name[0] = 0;
|
|
state->str_transferred[0] = 0;
|
|
state->str_timer[0] = 0;
|
|
state->str_rate[0] = 0;
|
|
state->str_average_rate[0] = 0;
|
|
state->str_progress[0] = 0;
|
|
state->str_eta[0] = 0;
|
|
memset(state->format, 0, sizeof(state->format));
|
|
|
|
if (state->name) {
|
|
sprintf(state->str_name, "%9.500s:", state->name);
|
|
}
|
|
|
|
formatstr =
|
|
state->format_string ? state->
|
|
format_string : state->default_format;
|
|
|
|
state->components_used = 0;
|
|
|
|
/*
|
|
* Split the format string into segments. Each segment consists
|
|
* of a string pointer and a length.
|
|
*
|
|
* A length of zero indicates that the segment is a fixed-size
|
|
* component updated by pv__format(), so it is a pointer to one
|
|
* of the state->str_* buffers that pv__format() updates.
|
|
*
|
|
* A length below zero indicates that the segment is a variable
|
|
* sized component which will be recalculated by pv__format()
|
|
* after the length of all fixed-size segments is known, and so
|
|
* the string is a pointer to another state->str_* buffer
|
|
* (currently it will only ever be state->str_progress).
|
|
*
|
|
* A length above zero indicates that the segment is a constant
|
|
* string of the given length (not necessarily null terminated).
|
|
*
|
|
* In pv__format(), after the state->str_* buffers have all been
|
|
* filled in, the output string is generated by sticking all of
|
|
* these segments together.
|
|
*/
|
|
segment = 0;
|
|
for (strpos = 0; formatstr[strpos] != 0 && segment < 99;
|
|
strpos++, segment++) {
|
|
if ('%' == formatstr[strpos]) {
|
|
unsigned int num;
|
|
strpos++;
|
|
num = 0;
|
|
while (isdigit(formatstr[strpos])) {
|
|
num = num * 10;
|
|
num += formatstr[strpos] - '0';
|
|
strpos++;
|
|
}
|
|
switch (formatstr[strpos]) {
|
|
case 'p':
|
|
state->format[segment].string =
|
|
state->str_progress;
|
|
state->format[segment].length = -1;
|
|
state->components_used |=
|
|
PV_DISPLAY_PROGRESS;
|
|
break;
|
|
case 't':
|
|
state->format[segment].string =
|
|
state->str_timer;
|
|
state->format[segment].length = 0;
|
|
state->components_used |= PV_DISPLAY_TIMER;
|
|
break;
|
|
case 'e':
|
|
state->format[segment].string =
|
|
state->str_eta;
|
|
state->format[segment].length = 0;
|
|
state->components_used |= PV_DISPLAY_ETA;
|
|
break;
|
|
case 'I':
|
|
state->format[segment].string =
|
|
state->str_fineta;
|
|
state->format[segment].length = 0;
|
|
state->components_used |=
|
|
PV_DISPLAY_FINETA;
|
|
break;
|
|
case 'A':
|
|
state->format[segment].string =
|
|
state->str_lastoutput;
|
|
state->format[segment].length = 0;
|
|
if (num > sizeof(state->lastoutput_buffer))
|
|
num =
|
|
sizeof
|
|
(state->lastoutput_buffer);
|
|
if (num < 1)
|
|
num = 1;
|
|
state->lastoutput_length = num;
|
|
state->components_used |=
|
|
PV_DISPLAY_OUTPUTBUF;
|
|
break;
|
|
case 'r':
|
|
state->format[segment].string =
|
|
state->str_rate;
|
|
state->format[segment].length = 0;
|
|
state->components_used |= PV_DISPLAY_RATE;
|
|
break;
|
|
case 'a':
|
|
state->format[segment].string =
|
|
state->str_average_rate;
|
|
state->format[segment].length = 0;
|
|
state->components_used |=
|
|
PV_DISPLAY_AVERAGERATE;
|
|
break;
|
|
case 'b':
|
|
state->format[segment].string =
|
|
state->str_transferred;
|
|
state->format[segment].length = 0;
|
|
state->components_used |= PV_DISPLAY_BYTES;
|
|
break;
|
|
case 'T':
|
|
state->format[segment].string =
|
|
state->str_bufpercent;
|
|
state->format[segment].length = 0;
|
|
state->components_used |=
|
|
PV_DISPLAY_BUFPERCENT;
|
|
break;
|
|
case 'N':
|
|
state->format[segment].string =
|
|
state->str_name;
|
|
state->format[segment].length =
|
|
strlen(state->str_name);
|
|
state->components_used |= PV_DISPLAY_NAME;
|
|
break;
|
|
case '%':
|
|
/* %% => % */
|
|
state->format[segment].string =
|
|
&(formatstr[strpos]);
|
|
state->format[segment].length = 1;
|
|
break;
|
|
case 0:
|
|
/* % at end => just % */
|
|
state->format[segment].string =
|
|
&(formatstr[--strpos]);
|
|
state->format[segment].length = 1;
|
|
break;
|
|
default:
|
|
/* %z (unknown) => %z */
|
|
state->format[segment].string =
|
|
&(formatstr[--strpos]);
|
|
state->format[segment].length = 2;
|
|
strpos++;
|
|
break;
|
|
}
|
|
} else {
|
|
int foundlength;
|
|
searchptr = strchr(&(formatstr[strpos]), '%');
|
|
if (NULL == searchptr) {
|
|
foundlength = strlen(&(formatstr[strpos]));
|
|
} else {
|
|
foundlength =
|
|
searchptr - &(formatstr[strpos]);
|
|
}
|
|
state->format[segment].string =
|
|
&(formatstr[strpos]);
|
|
state->format[segment].length = foundlength;
|
|
strpos += foundlength - 1;
|
|
}
|
|
}
|
|
|
|
state->format[segment].string = 0;
|
|
state->format[segment].length = 0;
|
|
}
|
|
|
|
/*
|
|
* Return the original value x so that it has been clamped between
|
|
* [min..max]
|
|
*/
|
|
static long bound_long(long x, long min, long max)
|
|
{
|
|
return x < min ? min : x > max ? max : x;
|
|
}
|
|
|
|
/* Update history and current average rate */
|
|
static void update_history_avg_rate(pvstate_t state, long long total_bytes,
|
|
long double elapsed_sec, long double rate)
|
|
{
|
|
int first = state->history_first;
|
|
int last = state->history_last;
|
|
long double last_elapsed = state->history[last].elapsed_sec;
|
|
|
|
if (!(last_elapsed == 0.0 || /* Empty */
|
|
elapsed_sec > last_elapsed + state->history_interval))
|
|
return;
|
|
|
|
if (last_elapsed) { /* Not empty, add new entry in circular buffer */
|
|
int len = state->history_len;
|
|
state->history_last = last = (last + 1) % len;
|
|
if (last == first)
|
|
state->history_first = first = (first + 1) % len;
|
|
}
|
|
|
|
state->history[last].elapsed_sec = elapsed_sec;
|
|
state->history[last].total_bytes = total_bytes;
|
|
|
|
if (first == last)
|
|
state->current_avg_rate = rate;
|
|
else {
|
|
long long bytes = (state->history[last].total_bytes -
|
|
state->history[first].total_bytes);
|
|
long double sec = (state->history[last].elapsed_sec -
|
|
state->history[first].elapsed_sec);
|
|
state->current_avg_rate = bytes / sec;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to a string (which must not be freed), containing status
|
|
* information formatted according to the state held within the given
|
|
* structure, where "elapsed_sec" is the seconds elapsed since the transfer
|
|
* started, "bytes_since_last" is the number of bytes transferred since the
|
|
* last update, and "total_bytes" is the total number of bytes transferred
|
|
* so far.
|
|
*
|
|
* If "bytes_since_last" is negative, this is the final update so the rate
|
|
* is given as an an average over the whole transfer; otherwise the current
|
|
* rate is shown.
|
|
*
|
|
* In line mode, "bytes_since_last" and "total_bytes" are in lines, not bytes.
|
|
*
|
|
* If "total_bytes" is negative, then free all allocated memory and return
|
|
* NULL.
|
|
*/
|
|
static char *pv__format(pvstate_t state,
|
|
long double elapsed_sec,
|
|
long long bytes_since_last, long long total_bytes)
|
|
{
|
|
long double time_since_last, rate, average_rate;
|
|
long eta;
|
|
int static_portion_size;
|
|
int segment;
|
|
int output_length;
|
|
int display_string_length;
|
|
|
|
/* Quick sanity check - state must exist */
|
|
if (NULL == state)
|
|
return NULL;
|
|
|
|
/* Negative total transfer - free memory and exit */
|
|
if (total_bytes < 0) {
|
|
if (state->display_buffer)
|
|
free(state->display_buffer);
|
|
state->display_buffer = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* In case the time since the last update is very small, we keep
|
|
* track of amount transferred since the last update, and just keep
|
|
* adding to that until a reasonable amount of time has passed to
|
|
* avoid rate spikes or division by zero.
|
|
*/
|
|
time_since_last = elapsed_sec - state->prev_elapsed_sec;
|
|
if (time_since_last <= 0.01) {
|
|
rate = state->prev_rate;
|
|
state->prev_trans += bytes_since_last;
|
|
} else {
|
|
rate =
|
|
((long double) bytes_since_last +
|
|
state->prev_trans) / time_since_last;
|
|
state->prev_elapsed_sec = elapsed_sec;
|
|
state->prev_trans = 0;
|
|
}
|
|
state->prev_rate = rate;
|
|
|
|
/* Update history and current average rate for ETA. */
|
|
update_history_avg_rate(state, total_bytes, elapsed_sec, rate);
|
|
average_rate = state->current_avg_rate;
|
|
|
|
if (state->size <= 0) {
|
|
/*
|
|
* If we don't know the total size of the incoming data,
|
|
* then for a percentage, we gradually increase the
|
|
* percentage completion as data arrives, to a maximum of
|
|
* 200, then reset it - we use this if we can't calculate
|
|
* it, so that the numeric percentage output will go
|
|
* 0%-100%, 100%-0%, 0%-100%, and so on.
|
|
*/
|
|
if (rate > 0)
|
|
state->percentage += 2;
|
|
if (state->percentage > 199)
|
|
state->percentage = 0;
|
|
} else if (state->numeric
|
|
|| ((state->components_used & PV_DISPLAY_PROGRESS) !=
|
|
0)) {
|
|
/*
|
|
* If we do know the total size, and we're going to show
|
|
* the percentage (numeric mode or a progress bar),
|
|
* calculate the percentage completion.
|
|
*/
|
|
state->percentage =
|
|
pv__calc_percentage(total_bytes, state->size);
|
|
}
|
|
|
|
/*
|
|
* Reallocate output buffer if width changes.
|
|
*/
|
|
if (state->display_buffer != NULL
|
|
&& state->display_buffer_size < (state->width * 2)) {
|
|
free(state->display_buffer);
|
|
state->display_buffer = NULL;
|
|
state->display_buffer_size = 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate output buffer if there isn't one.
|
|
*/
|
|
if (NULL == state->display_buffer) {
|
|
state->display_buffer_size = (2 * state->width) + 80;
|
|
if (state->name)
|
|
state->display_buffer_size += strlen(state->name);
|
|
state->display_buffer =
|
|
malloc(state->display_buffer_size + 16);
|
|
if (NULL == state->display_buffer) {
|
|
pv_error(state, "%s: %s",
|
|
_("buffer allocation failed"),
|
|
strerror(errno));
|
|
state->exit_status |= 64;
|
|
return NULL;
|
|
}
|
|
state->display_buffer[0] = 0;
|
|
}
|
|
|
|
/*
|
|
* In numeric output mode, our output is just a number.
|
|
*
|
|
* Patch from Sami Liedes:
|
|
* With --timer we prefix the output with the elapsed time.
|
|
* With --bytes we output the bytes transferred so far instead
|
|
* of the percentage. (Or lines, if --lines was given with --bytes).
|
|
*/
|
|
if (state->numeric) {
|
|
char numericprefix[128];
|
|
|
|
numericprefix[0] = 0;
|
|
|
|
if ((state->components_used & PV_DISPLAY_TIMER) != 0)
|
|
sprintf(numericprefix, "%.4Lf ", elapsed_sec);
|
|
|
|
if ((state->components_used & PV_DISPLAY_BYTES) != 0) {
|
|
sprintf(state->display_buffer, "%.99s%lld\n",
|
|
numericprefix, total_bytes);
|
|
} else if (state->percentage > 100) {
|
|
/* As mentioned above, we go 0-100, then 100-0. */
|
|
sprintf(state->display_buffer, "%.99s%ld\n",
|
|
numericprefix, 200 - state->percentage);
|
|
} else {
|
|
sprintf(state->display_buffer, "%.99s%ld\n",
|
|
numericprefix, state->percentage);
|
|
}
|
|
|
|
return state->display_buffer;
|
|
}
|
|
|
|
/*
|
|
* First, work out what components we will be putting in the output
|
|
* buffer, and for those that don't depend on the total width
|
|
* available (i.e. all but the progress bar), prepare their strings
|
|
* to be placed in the output buffer.
|
|
*/
|
|
|
|
state->str_transferred[0] = 0;
|
|
state->str_bufpercent[0] = 0;
|
|
state->str_timer[0] = 0;
|
|
state->str_rate[0] = 0;
|
|
state->str_average_rate[0] = 0;
|
|
state->str_progress[0] = 0;
|
|
state->str_lastoutput[0] = 0;
|
|
state->str_eta[0] = 0;
|
|
state->str_fineta[0] = 0;
|
|
|
|
/* If we're showing bytes transferred, set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_BYTES) != 0) {
|
|
pv__sizestr(state->str_transferred,
|
|
sizeof(state->str_transferred), "%s",
|
|
(long double) total_bytes, "", _("B"),
|
|
state->linemode ? 0 : 1);
|
|
}
|
|
|
|
/* Transfer buffer percentage - set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_BUFPERCENT) != 0) {
|
|
if (state->buffer_size > 0)
|
|
sprintf(state->str_bufpercent, "{%3ld%%}",
|
|
pv__calc_percentage(state->read_position -
|
|
state->write_position,
|
|
state->buffer_size));
|
|
#ifdef HAVE_SPLICE
|
|
if (state->splice_used)
|
|
strcpy(state->str_bufpercent, "{----}");
|
|
#endif
|
|
}
|
|
|
|
/* Timer - set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_TIMER) != 0) {
|
|
/*
|
|
* Bounds check, so we don't overrun the prefix buffer. This
|
|
* does mean that the timer will stop at a 100,000 hours,
|
|
* but since that's 11 years, it shouldn't be a problem.
|
|
*/
|
|
if (elapsed_sec > (long double) 360000000.0L)
|
|
elapsed_sec = (long double) 360000000.0L;
|
|
|
|
sprintf(state->str_timer, "%ld:%02ld:%02ld",
|
|
((long) elapsed_sec) / 3600,
|
|
(((long) elapsed_sec) / 60) % 60,
|
|
((long) elapsed_sec) % 60);
|
|
}
|
|
|
|
/* Rate - set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_RATE) != 0) {
|
|
pv__sizestr(state->str_rate, sizeof(state->str_rate),
|
|
"[%s]", rate, _("/s"), _("B/s"),
|
|
state->linemode ? 0 : 1);
|
|
}
|
|
|
|
/* Average rate - set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_AVERAGERATE) != 0) {
|
|
pv__sizestr(state->str_average_rate,
|
|
sizeof(state->str_average_rate), "[%s]",
|
|
average_rate, _("/s"), _("B/s"),
|
|
state->linemode ? 0 : 1);
|
|
}
|
|
|
|
/* Last output bytes - set up the display string. */
|
|
if ((state->components_used & PV_DISPLAY_OUTPUTBUF) != 0) {
|
|
int idx;
|
|
for (idx = 0; idx < state->lastoutput_length; idx++) {
|
|
int c;
|
|
c = state->lastoutput_buffer[idx];
|
|
state->str_lastoutput[idx] = isprint(c) ? c : '.';
|
|
}
|
|
state->str_lastoutput[idx] = 0;
|
|
}
|
|
|
|
/* ETA (only if size is known) - set up the display string. */
|
|
if (((state->components_used & PV_DISPLAY_ETA) != 0)
|
|
&& (state->size > 0)) {
|
|
eta =
|
|
pv__calc_eta(total_bytes - state->initial_offset,
|
|
state->size - state->initial_offset,
|
|
state->current_avg_rate);
|
|
|
|
/*
|
|
* Bounds check, so we don't overrun the suffix buffer. This
|
|
* means the ETA will always be less than 100,000 hours.
|
|
*/
|
|
eta = bound_long(eta, 0, (long) 360000000L);
|
|
|
|
/*
|
|
* If the ETA is more than a day, include a day count as
|
|
* well as hours, minutes, and seconds.
|
|
*/
|
|
if (eta > 86400L) {
|
|
sprintf(state->str_eta,
|
|
"%.16s %ld:%02ld:%02ld:%02ld", _("ETA"),
|
|
eta / 86400, (eta / 3600) % 24,
|
|
(eta / 60) % 60, eta % 60);
|
|
} else {
|
|
sprintf(state->str_eta, "%.16s %ld:%02ld:%02ld",
|
|
_("ETA"), eta / 3600, (eta / 60) % 60,
|
|
eta % 60);
|
|
}
|
|
|
|
/*
|
|
* If this is the final update, show a blank space where the
|
|
* ETA used to be.
|
|
*/
|
|
if (bytes_since_last < 0) {
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(state->str_eta)
|
|
&& state->str_eta[i] != 0; i++) {
|
|
state->str_eta[i] = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ETA as clock time (as above) - set up the display string. */
|
|
if (((state->components_used & PV_DISPLAY_FINETA) != 0)
|
|
&& (state->size > 0)) {
|
|
/*
|
|
* The ETA may be hidden by a failed ETA string
|
|
* generation.
|
|
*/
|
|
int show_eta = 1;
|
|
time_t now = time(NULL);
|
|
time_t then;
|
|
struct tm *time_ptr;
|
|
char *time_format = NULL;
|
|
|
|
eta =
|
|
pv__calc_eta(total_bytes - state->initial_offset,
|
|
state->size - state->initial_offset,
|
|
state->current_avg_rate);
|
|
|
|
/*
|
|
* Bounds check, so we don't overrun the suffix buffer. This
|
|
* means the ETA will always be less than 100,000 hours.
|
|
*/
|
|
eta = bound_long(eta, 0, (long) 360000000L);
|
|
|
|
/*
|
|
* Only include the date if the ETA is more than 6 hours
|
|
* away.
|
|
*/
|
|
if (eta > (long) (6 * 3600)) {
|
|
time_format = "%Y-%m-%d %H:%M:%S";
|
|
} else {
|
|
time_format = "%H:%M:%S";
|
|
}
|
|
|
|
then = now + eta;
|
|
time_ptr = localtime(&then);
|
|
|
|
if (NULL == time_ptr) {
|
|
show_eta = 0;
|
|
} else {
|
|
/* Localtime keeps data stored in a
|
|
* static buffer that gets overwritten
|
|
* by time functions. */
|
|
struct tm time = *time_ptr;
|
|
|
|
sprintf(state->str_fineta, "%.16s ", _("ETA"));
|
|
strftime(state->str_fineta +
|
|
strlen(state->str_fineta),
|
|
sizeof(state->str_fineta) - 1 -
|
|
strlen(state->str_fineta),
|
|
time_format, &time);
|
|
}
|
|
|
|
if (!show_eta) {
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(state->str_fineta)
|
|
&& state->str_fineta[i] != 0; i++) {
|
|
state->str_fineta[i] = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now go through all the static portions of the format to work
|
|
* out how much space will be left for any dynamic portions
|
|
* (i.e. the progress bar).
|
|
*/
|
|
static_portion_size = 0;
|
|
for (segment = 0; state->format[segment].string; segment++) {
|
|
if (state->format[segment].length < 0) {
|
|
continue;
|
|
} else if (state->format[segment].length > 0) {
|
|
static_portion_size +=
|
|
state->format[segment].length;
|
|
} else {
|
|
static_portion_size +=
|
|
strlen(state->format[segment].string);
|
|
}
|
|
}
|
|
|
|
debug("static_portion_size: %d", static_portion_size);
|
|
|
|
/*
|
|
* Assemble the progress bar now we know how big it should be.
|
|
*/
|
|
if ((state->components_used & PV_DISPLAY_PROGRESS) != 0) {
|
|
char pct[16];
|
|
int available_width, i;
|
|
|
|
strcpy(state->str_progress, "[");
|
|
|
|
if (state->size > 0) {
|
|
if (state->percentage < 0)
|
|
state->percentage = 0;
|
|
if (state->percentage > 100000)
|
|
state->percentage = 100000;
|
|
sprintf(pct, "%2ld%%", state->percentage);
|
|
|
|
available_width =
|
|
state->width - static_portion_size -
|
|
strlen(pct) - 3;
|
|
|
|
if (available_width < 0)
|
|
available_width = 0;
|
|
|
|
if (available_width >
|
|
(int) (sizeof(state->str_progress)) - 16)
|
|
available_width =
|
|
sizeof(state->str_progress) - 16;
|
|
|
|
for (i = 0;
|
|
i <
|
|
(available_width * state->percentage) / 100 -
|
|
1; i++) {
|
|
if (i < available_width)
|
|
strcat(state->str_progress, "=");
|
|
}
|
|
if (i < available_width) {
|
|
strcat(state->str_progress, ">");
|
|
i++;
|
|
}
|
|
for (; i < available_width; i++) {
|
|
strcat(state->str_progress, " ");
|
|
}
|
|
strcat(state->str_progress, "] ");
|
|
strcat(state->str_progress, pct);
|
|
} else {
|
|
int p = state->percentage;
|
|
|
|
available_width =
|
|
state->width - static_portion_size - 5;
|
|
|
|
if (available_width < 0)
|
|
available_width = 0;
|
|
|
|
if (available_width >
|
|
(int) (sizeof(state->str_progress)) - 16)
|
|
available_width =
|
|
sizeof(state->str_progress) - 16;
|
|
|
|
debug("available_width: %d", available_width);
|
|
|
|
if (p > 100)
|
|
p = 200 - p;
|
|
for (i = 0; i < (available_width * p) / 100; i++) {
|
|
if (i < available_width)
|
|
strcat(state->str_progress, " ");
|
|
}
|
|
strcat(state->str_progress, "<=>");
|
|
for (; i < available_width; i++) {
|
|
strcat(state->str_progress, " ");
|
|
}
|
|
strcat(state->str_progress, "]");
|
|
}
|
|
|
|
/*
|
|
* If the progress bar won't fit, drop it.
|
|
*/
|
|
if (strlen(state->str_progress) + static_portion_size >
|
|
state->width)
|
|
state->str_progress[0] = 0;
|
|
}
|
|
|
|
/*
|
|
* We can now build the output string using the format structure.
|
|
*/
|
|
|
|
state->display_buffer[0] = 0;
|
|
display_string_length = 0;
|
|
for (segment = 0; state->format[segment].string; segment++) {
|
|
int segment_length;
|
|
if (state->format[segment].length > 0) {
|
|
segment_length = state->format[segment].length;
|
|
} else {
|
|
segment_length =
|
|
strlen(state->format[segment].string);
|
|
}
|
|
/* Skip empty segments */
|
|
if (segment_length == 0)
|
|
continue;
|
|
/*
|
|
* Truncate segment if it would make the display string
|
|
* overflow the buffer
|
|
*/
|
|
if (segment_length + display_string_length >
|
|
state->display_buffer_size - 2)
|
|
segment_length =
|
|
state->display_buffer_size -
|
|
display_string_length - 2;
|
|
if (segment_length < 1)
|
|
break;
|
|
/* Skip segment if it would make the display too wide */
|
|
if (segment_length + display_string_length >
|
|
(int) (state->width))
|
|
break;
|
|
strncat(state->display_buffer,
|
|
state->format[segment].string, segment_length);
|
|
display_string_length += segment_length;
|
|
}
|
|
|
|
/*
|
|
* If the size of our output shrinks, we need to keep appending
|
|
* spaces at the end, so that we don't leave dangling bits behind.
|
|
*/
|
|
output_length = strlen(state->display_buffer);
|
|
if ((output_length < state->prev_length)
|
|
&& ((int) (state->width) >= state->prev_width)) {
|
|
char spaces[32];
|
|
int spaces_to_add;
|
|
spaces_to_add = state->prev_length - output_length;
|
|
/* Upper boundary on number of spaces */
|
|
if (spaces_to_add > 15) {
|
|
spaces_to_add = 15;
|
|
}
|
|
output_length += spaces_to_add;
|
|
spaces[spaces_to_add] = 0;
|
|
while (--spaces_to_add >= 0) {
|
|
spaces[spaces_to_add] = ' ';
|
|
}
|
|
strcat(state->display_buffer, spaces);
|
|
}
|
|
state->prev_width = state->width;
|
|
state->prev_length = output_length;
|
|
|
|
return state->display_buffer;
|
|
}
|
|
|
|
|
|
/*
|
|
* Output status information on standard error, where "esec" is the seconds
|
|
* elapsed since the transfer started, "sl" is the number of bytes transferred
|
|
* since the last update, and "tot" is the total number of bytes transferred
|
|
* so far.
|
|
*
|
|
* If "sl" is negative, this is the final update so the rate is given as an
|
|
* an average over the whole transfer; otherwise the current rate is shown.
|
|
*
|
|
* In line mode, "sl" and "tot" are in lines, not bytes.
|
|
*/
|
|
void pv_display(pvstate_t state, long double esec, long long sl,
|
|
long long tot)
|
|
{
|
|
char *display;
|
|
|
|
if (NULL == state)
|
|
return;
|
|
|
|
/*
|
|
* If the display options need reparsing, do so to generate new
|
|
* formatting parameters.
|
|
*/
|
|
if (state->reparse_display) {
|
|
pv__format_init(state);
|
|
state->reparse_display = 0;
|
|
}
|
|
|
|
pv_sig_checkbg();
|
|
|
|
display = pv__format(state, esec, sl, tot);
|
|
if (NULL == display)
|
|
return;
|
|
|
|
if (state->numeric) {
|
|
write(STDERR_FILENO, display, strlen(display));
|
|
} else if (state->cursor) {
|
|
if (pv_in_foreground()) {
|
|
pv_crs_update(state, display);
|
|
state->display_visible = true;
|
|
}
|
|
} else {
|
|
if (pv_in_foreground()) {
|
|
write(STDERR_FILENO, display, strlen(display));
|
|
write(STDERR_FILENO, "\r", 1);
|
|
state->display_visible = true;
|
|
}
|
|
}
|
|
|
|
debug("%s: [%s]", "display", display);
|
|
}
|
|
|
|
/* EOF */
|