#define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
-/* Fallnback progress implementation should be something that works
+/* Fallback progress implementation should be something that works
under all display types. If you put something other than "dot"
here, remember that bar_set_params tries to switch to this if we're
not running on a TTY. So changing this to "bar" could cause
{
int i, namelen;
struct progress_implementation *pi = implementations;
- char *colon;
+ const char *colon;
if (!name)
name = DEFAULT_PROGRESS_IMPLEMENTATION;
wgint total_length; /* expected total byte count when the
download finishes */
- int accumulated;
+ int accumulated; /* number of bytes accumulated after
+ the last printed dot */
int rows; /* number of rows printed so far */
int dots; /* number of dots printed in this row */
+
double last_timer_value;
};
if (dp->initial_length)
{
int dot_bytes = opt.dot_bytes;
- wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
+ const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
- int remainder = (int) (dp->initial_length % row_bytes);
+ int remainder = dp->initial_length % ROW_BYTES;
wgint skipped = dp->initial_length - remainder;
if (skipped)
{
- int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
+ wgint skipped_k = skipped / 1024; /* skipped amount in K */
int skipped_k_len = numdigit (skipped_k);
- if (skipped_k_len < 5)
- skipped_k_len = 5;
+ if (skipped_k_len < 6)
+ skipped_k_len = 6;
/* Align the [ skipping ... ] line with the dots. To do
that, insert the number of spaces equal to the number of
digits in the skipped amount in K. */
- logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"),
- 2 + skipped_k_len, "", skipped_k);
+ logprintf (LOG_VERBOSE, _("\n%*s[ skipping %sK ]"),
+ 2 + skipped_k_len, "",
+ number_to_static_string (skipped_k));
}
- logprintf (LOG_VERBOSE, "\n%5ldK", (long) (skipped / 1024));
+ logprintf (LOG_VERBOSE, "\n%6sK",
+ number_to_static_string (skipped / 1024));
for (; remainder >= dot_bytes; remainder -= dot_bytes)
{
if (dp->dots % opt.dot_spacing == 0)
assert (dp->dots < opt.dots_in_line);
dp->accumulated = remainder;
- dp->rows = skipped / row_bytes;
+ dp->rows = skipped / ROW_BYTES;
}
return dp;
}
-static void
-print_percentage (wgint bytes, wgint expected)
-{
- int percentage = (int)(100.0 * bytes / expected);
- logprintf (LOG_VERBOSE, "%3d%%", percentage);
-}
+static const char *eta_to_human_short (int, bool);
+
+/* Prints the stats (percentage of completion, speed, ETA) for current
+ row. DLTIME is the time spent downloading the data in current
+ row.
+
+ #### This function is somewhat uglified by the fact that current
+ row and last row have somewhat different stats requirements. It
+ might be worthwhile to split it to two different functions. */
static void
-print_download_speed (struct dot_progress *dp, wgint bytes, double dltime)
+print_row_stats (struct dot_progress *dp, double dltime, bool last)
{
- logprintf (LOG_VERBOSE, " %s",
- retr_rate (bytes, dltime - dp->last_timer_value, 1));
- dp->last_timer_value = dltime;
+ const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
+
+ /* bytes_displayed is the number of bytes indicated to the user by
+ dots printed so far, includes the initially "skipped" amount */
+ wgint bytes_displayed = dp->rows * ROW_BYTES + dp->dots * opt.dot_bytes;
+
+ if (last)
+ /* For last row also count bytes accumulated after last dot */
+ bytes_displayed += dp->accumulated;
+
+ if (dp->total_length)
+ {
+ /* Round to floor value to provide gauge how much data *has*
+ been retrieved. 12.8% will round to 12% because the 13% mark
+ has not yet been reached. 100% is only shown when done. */
+ int percentage = 100.0 * bytes_displayed / dp->total_length;
+ logprintf (LOG_VERBOSE, "%3d%%", percentage);
+ }
+
+ {
+ static char names[] = {' ', 'K', 'M', 'G'};
+ int units;
+ double rate;
+ wgint bytes_this_row;
+ if (!last)
+ bytes_this_row = ROW_BYTES;
+ else
+ /* For last row also include bytes accumulated after last dot. */
+ bytes_this_row = dp->dots * opt.dot_bytes + dp->accumulated;
+ /* Don't count the portion of the row belonging to initial_length */
+ if (dp->rows == dp->initial_length / ROW_BYTES)
+ bytes_this_row -= dp->initial_length % ROW_BYTES;
+ rate = calc_rate (bytes_this_row, dltime - dp->last_timer_value, &units);
+ logprintf (LOG_VERBOSE, " %4.*f%c",
+ rate >= 99.95 ? 0 : rate >= 9.995 ? 1 : 2,
+ rate, names[units]);
+ dp->last_timer_value = dltime;
+ }
+
+ if (!last)
+ {
+ /* Display ETA based on average speed. Inspired by Vladi
+ Belperchinov-Shabanski's "wget-new-percentage" patch. */
+ if (dp->total_length)
+ {
+ wgint bytes_remaining = dp->total_length - bytes_displayed;
+ /* The quantity downloaded in this download run. */
+ wgint bytes_sofar = bytes_displayed - dp->initial_length;
+ double secs_sofar = dltime / 1000;
+ int eta = (int) (secs_sofar * bytes_remaining / bytes_sofar + 0.5);
+ logprintf (LOG_VERBOSE, " %s", eta_to_human_short (eta, true));
+ }
+ }
+ else
+ {
+ /* When done, print the total download time */
+ double secs = dltime / 1000;
+ if (secs >= 10)
+ logprintf (LOG_VERBOSE, "=%s",
+ eta_to_human_short ((int) (secs + 0.5), true));
+ else
+ logprintf (LOG_VERBOSE, "=%ss", print_decimal (secs));
+ }
}
/* Dot-progress backend for progress_update. */
{
struct dot_progress *dp = progress;
int dot_bytes = opt.dot_bytes;
- wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
+ wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
log_set_flush (false);
for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
{
if (dp->dots == 0)
- logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024));
+ logprintf (LOG_VERBOSE, "\n%6sK",
+ number_to_static_string (dp->rows * ROW_BYTES / 1024));
if (dp->dots % opt.dot_spacing == 0)
logputs (LOG_VERBOSE, " ");
++dp->dots;
if (dp->dots >= opt.dots_in_line)
{
- wgint row_qty = row_bytes;
- if (dp->rows == dp->initial_length / row_bytes)
- row_qty -= dp->initial_length % row_bytes;
-
++dp->rows;
dp->dots = 0;
- if (dp->total_length)
- print_percentage (dp->rows * row_bytes, dp->total_length);
- print_download_speed (dp, row_qty, dltime);
+ print_row_stats (dp, dltime, false);
}
}
dot_finish (void *progress, double dltime)
{
struct dot_progress *dp = progress;
- int dot_bytes = opt.dot_bytes;
- wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
+ wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
int i;
log_set_flush (false);
if (dp->dots == 0)
- logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024));
+ logprintf (LOG_VERBOSE, "\n%6sK",
+ number_to_static_string (dp->rows * ROW_BYTES / 1024));
for (i = dp->dots; i < opt.dots_in_line; i++)
{
if (i % opt.dot_spacing == 0)
logputs (LOG_VERBOSE, " ");
logputs (LOG_VERBOSE, " ");
}
- if (dp->total_length)
- {
- print_percentage (dp->rows * row_bytes
- + dp->dots * dot_bytes
- + dp->accumulated,
- dp->total_length);
- }
-
- {
- wgint row_qty = dp->dots * dot_bytes + dp->accumulated;
- if (dp->rows == dp->initial_length / row_bytes)
- row_qty -= dp->initial_length % row_bytes;
- print_download_speed (dp, row_qty, dltime);
- }
+ print_row_stats (dp, dltime, true);
logputs (LOG_VERBOSE, "\n\n");
log_set_flush (false);
int last_eta_value;
};
-static void create_image (struct bar_progress *, double);
+static void create_image (struct bar_progress *, double, bool);
static void display_image (char *);
static void *
logputs (LOG_VERBOSE, "\n");
- create_image (bp, 0);
+ create_image (bp, 0, false);
display_image (bp->buffer);
return bp;
/* Don't update more often than five times per second. */
return;
- create_image (bp, dltime);
+ create_image (bp, dltime, false);
display_image (bp->buffer);
bp->last_screen_update = dltime;
}
/* See bar_update() for explanation. */
bp->total_length = bp->initial_length + bp->count;
- create_image (bp, dltime);
+ create_image (bp, dltime, true);
display_image (bp->buffer);
logputs (LOG_VERBOSE, "\n\n");
#endif
}
-static const char *eta_to_human (int);
-
#define APPEND_LITERAL(s) do { \
memcpy (p, s, sizeof (s) - 1); \
p += sizeof (s) - 1; \
} while (0)
+/* Use move_to_end (s) to get S to point the end of the string (the
+ terminating \0). This is faster than s+=strlen(s), but some people
+ are confused when they see strchr (s, '\0') in the code. */
+#define move_to_end(s) s = strchr (s, '\0');
+
#ifndef MAX
# define MAX(a, b) ((a) >= (b) ? (a) : (b))
#endif
static void
-create_image (struct bar_progress *bp, double dl_total_time)
+create_image (struct bar_progress *bp, double dl_total_time, bool done)
{
char *p = bp->buffer;
wgint size = bp->initial_length + bp->count;
- char *size_legible = with_thousand_seps (size);
- int size_legible_len = strlen (size_legible);
+ const char *size_grouped = with_thousand_seps (size);
+ int size_grouped_len = strlen (size_grouped);
struct bar_progress_hist *hist = &bp->hist;
"xx% " or "100%" - percentage - 4 chars
"[]" - progress bar decorations - 2 chars
" nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
- " 1012.56K/s" - dl rate - 11 chars
+ " 12.5K/s" - download rate - 8 chars
" eta 36m 51s" - ETA - 13 chars
"=====>..." - progress bar - the rest
*/
- int dlbytes_size = 1 + MAX (size_legible_len, 11);
- int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
+ int dlbytes_size = 1 + MAX (size_grouped_len, 11);
+ int progress_size = bp->width - (4 + 2 + dlbytes_size + 8 + 13);
if (progress_size < 5)
progress_size = 0;
/* "xx% " */
if (bp->total_length > 0)
{
- int percentage = (int)(100.0 * size / bp->total_length);
+ int percentage = 100.0 * size / bp->total_length;
assert (percentage <= 100);
if (percentage < 100)
}
/* " 234,567,890" */
- sprintf (p, " %-11s", with_thousand_seps (size));
- p += strlen (p);
+ sprintf (p, " %-11s", size_grouped);
+ move_to_end (p);
- /* " 1012.45K/s" */
+ /* " 12.52K/s" */
if (hist->total_time && hist->total_bytes)
{
static const char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
wgint dlquant = hist->total_bytes + bp->recent_bytes;
double dltime = hist->total_time + (dl_total_time - bp->recent_start);
double dlspeed = calc_rate (dlquant, dltime, &units);
- sprintf (p, " %7.2f%s", dlspeed, short_units[units]);
- p += strlen (p);
+ sprintf (p, " %4.*f%s", dlspeed >= 99.95 ? 0 : dlspeed >= 9.995 ? 1 : 2,
+ dlspeed, short_units[units]);
+ move_to_end (p);
}
else
- APPEND_LITERAL (" --.--K/s");
+ APPEND_LITERAL (" --.-K/s");
- /* " ETA ..m ..s"; wait for three seconds before displaying the ETA.
- That's because the ETA value needs a while to become
- reliable. */
- if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3000)
+ if (!done)
{
- int eta;
-
- /* Don't change the value of ETA more than approximately once
- per second; doing so would cause flashing without providing
- any value to the user. */
- if (bp->total_length != size
- && bp->last_eta_value != 0
- && dl_total_time - bp->last_eta_time < 900)
- eta = bp->last_eta_value;
- else
+ /* " eta ..m ..s"; wait for three seconds before displaying the ETA.
+ That's because the ETA value needs a while to become
+ reliable. */
+ if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3000)
{
- /* Calculate ETA using the average download speed to predict
- the future speed. If you want to use a speed averaged
- over a more recent period, replace dl_total_time with
- hist->total_time and bp->count with hist->total_bytes.
- I found that doing that results in a very jerky and
- ultimately unreliable ETA. */
- double time_sofar = (double) dl_total_time / 1000;
- wgint bytes_remaining = bp->total_length - size;
- eta = (int) (time_sofar * bytes_remaining / bp->count + 0.5);
- bp->last_eta_value = eta;
- bp->last_eta_time = dl_total_time;
+ int eta;
+
+ /* Don't change the value of ETA more than approximately once
+ per second; doing so would cause flashing without providing
+ any value to the user. */
+ if (bp->total_length != size
+ && bp->last_eta_value != 0
+ && dl_total_time - bp->last_eta_time < 900)
+ eta = bp->last_eta_value;
+ else
+ {
+ /* Calculate ETA using the average download speed to predict
+ the future speed. If you want to use a speed averaged
+ over a more recent period, replace dl_total_time with
+ hist->total_time and bp->count with hist->total_bytes.
+ I found that doing that results in a very jerky and
+ ultimately unreliable ETA. */
+ double time_sofar = (double) dl_total_time / 1000;
+ wgint bytes_remaining = bp->total_length - size;
+ eta = (int) (time_sofar * bytes_remaining / bp->count + 0.5);
+ bp->last_eta_value = eta;
+ bp->last_eta_time = dl_total_time;
+ }
+
+ /* Translation note: "ETA" is English-centric, but this must
+ be short, ideally 3 chars. Abbreviate if necessary. */
+ sprintf (p, _(" eta %s"), eta_to_human_short (eta, false));
+ move_to_end (p);
+ }
+ else if (bp->total_length > 0)
+ {
+ APPEND_LITERAL (" ");
}
-
- sprintf (p, " eta %s", eta_to_human (eta));
- p += strlen (p);
}
- else if (bp->total_length > 0)
+ else
{
- APPEND_LITERAL (" ");
+ /* When the download is done, print the elapsed time. */
+ double secs = dl_total_time / 1000;
+ /* Note to translators: this should not take up more room than
+ available here. Abbreviate if necessary. */
+ strcpy (p, _(" in "));
+ move_to_end (p); /* not p+=6, think translations! */
+ if (secs >= 10)
+ strcpy (p, eta_to_human_short ((int) (secs + 0.5), false));
+ else
+ sprintf (p, "%ss", print_decimal (secs));
+ move_to_end (p);
}
assert (p - bp->buffer <= bp->width);
}
#endif
-/* Provide a human-readable rendition of the ETA. It never occupies
- more than 7 characters of screen space. */
+/* Provide a short human-readable rendition of the ETA. This is like
+ secs_to_human_time in main.c, except the output doesn't include
+ fractions (which would look silly in by nature imprecise ETA) and
+ takes less room. If the time is measured in hours, hours and
+ minutes (but not seconds) are shown; if measured in days, then days
+ and hours are shown. This ensures brevity while still displaying
+ as much as possible.
+
+ If CONDENSED is true, the separator between minutes and seconds
+ (and hours and minutes, etc.) is not included, shortening the
+ display by one additional character. This is used for dot
+ progress.
+
+ The display never occupies more than 7 characters of screen
+ space. */
static const char *
-eta_to_human (int secs)
+eta_to_human_short (int secs, bool condensed)
{
- static char buf[10]; /* 8 is enough, but just in case */
+ static char buf[10]; /* 8 should be enough, but just in case */
static int last = -1;
+ const char *space = condensed ? "" : " ";
- /* Trivial optimization. This function can be called every 200
- msecs (see bar_update) for fast downloads, but ETA will only
- change once per 900 msecs (see create_image). */
+ /* Trivial optimization. create_image can call us every 200 msecs
+ (see bar_update) for fast downloads, but ETA will only change
+ once per 900 msecs. */
if (secs == last)
return buf;
last = secs;
if (secs < 100)
sprintf (buf, "%ds", secs);
else if (secs < 100 * 60)
- sprintf (buf, "%dm %ds", secs / 60, secs % 60);
+ sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60);
else if (secs < 100 * 3600)
- sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60);
+ sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60);
else if (secs < 100 * 86400)
- sprintf (buf, "%dd %dh", secs / 86400, (secs / 3600) % 60);
+ sprintf (buf, "%dd%s%dh", secs / 86400, space, (secs / 3600) % 60);
else
- /* (2^31-1)/86400 doesn't overflow BUF. */
+ /* even (2^31-1)/86400 doesn't overflow BUF. */
sprintf (buf, "%dd", secs / 86400);
return buf;