X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fprogress.c;h=6f24cd290e027094504b7479910e109141256f33;hb=bb0194c6e7181825cd6a1cf6440c05d9f99f6449;hp=c4dd737e6d269df90baaef1ed951aee777663917;hpb=dd84231c6a02cfc885b796bea6ad8cb2fc184e9b;p=wget diff --git a/src/progress.c b/src/progress.c index c4dd737e..6f24cd29 100644 --- a/src/progress.c +++ b/src/progress.c @@ -1,5 +1,5 @@ /* Download progress. - Copyright (C) 2001 Free Software Foundation, Inc. + Copyright (C) 2001, 2002 Free Software Foundation, Inc. This file is part of GNU Wget. @@ -15,24 +15,28 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +In addition, as a special exception, the Free Software Foundation +gives permission to link the code of its release of Wget with the +OpenSSL project's "OpenSSL" library (or with modified versions of it +that use the same license as the "OpenSSL" library), and distribute +the linked executables. You must obey the GNU General Public License +in all respects for all of the code used other than "OpenSSL". If you +modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do +so, delete this exception statement from your version. */ #include #include #include -#ifdef HAVE_STRING_H -# include -#else -# include -#endif /* HAVE_STRING_H */ +#include #include #ifdef HAVE_UNISTD_H # include #endif -#ifdef HAVE_SIGNAL_H -# include -#endif +#include #include "wget.h" #include "progress.h" @@ -40,28 +44,29 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "retr.h" struct progress_implementation { - char *name; - void *(*create) (long, long); - void (*update) (void *, long, long); - void (*finish) (void *, long); + const char *name; + bool interactive; + void *(*create) (wgint, wgint); + void (*update) (void *, wgint, double); + void (*finish) (void *, double); void (*set_params) (const char *); }; /* Necessary forward declarations. */ -static void *dot_create PARAMS ((long, long)); -static void dot_update PARAMS ((void *, long, long)); -static void dot_finish PARAMS ((void *, long)); -static void dot_set_params PARAMS ((const char *)); +static void *dot_create (wgint, wgint); +static void dot_update (void *, wgint, double); +static void dot_finish (void *, double); +static void dot_set_params (const char *); -static void *bar_create PARAMS ((long, long)); -static void bar_update PARAMS ((void *, long, long)); -static void bar_finish PARAMS ((void *, long)); -static void bar_set_params PARAMS ((const char *)); +static void *bar_create (wgint, wgint); +static void bar_update (void *, wgint, double); +static void bar_finish (void *, double); +static void bar_set_params (const char *); static struct progress_implementation implementations[] = { - { "dot", dot_create, dot_update, dot_finish, dot_set_params }, - { "bar", bar_create, bar_update, bar_finish, bar_set_params } + { "dot", 0, dot_create, dot_update, dot_finish, dot_set_params }, + { "bar", 1, bar_create, bar_update, bar_finish, bar_set_params } }; static struct progress_implementation *current_impl; static int current_impl_locked; @@ -71,7 +76,7 @@ static int current_impl_locked; #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 @@ -79,21 +84,21 @@ static int current_impl_locked; #define FALLBACK_PROGRESS_IMPLEMENTATION "dot" -/* Return non-zero if NAME names a valid progress bar implementation. - The characters after the first : will be ignored. */ +/* Return true if NAME names a valid progress bar implementation. The + characters after the first : will be ignored. */ -int +bool valid_progress_implementation_p (const char *name) { - int i = 0; + int i; struct progress_implementation *pi = implementations; char *colon = strchr (name, ':'); int namelen = colon ? colon - name : strlen (name); - for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++) + for (i = 0; i < countof (implementations); i++, pi++) if (!strncmp (pi->name, name, namelen)) - return 1; - return 0; + return true; + return false; } /* Set the progress implementation to NAME. */ @@ -103,7 +108,7 @@ set_progress_implementation (const char *name) { int i, namelen; struct progress_implementation *pi = implementations; - char *colon; + const char *colon; if (!name) name = DEFAULT_PROGRESS_IMPLEMENTATION; @@ -111,7 +116,7 @@ set_progress_implementation (const char *name) colon = strchr (name, ':'); namelen = colon ? colon - name : strlen (name); - for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++) + for (i = 0; i < countof (implementations); i++, pi++) if (!strncmp (pi->name, name, namelen)) { current_impl = pi; @@ -145,7 +150,7 @@ progress_schedule_redirect (void) advance. */ void * -progress_create (long initial, long total) +progress_create (wgint initial, wgint total) { /* Check if the log status has changed under our feet. */ if (output_redirected) @@ -158,11 +163,22 @@ progress_create (long initial, long total) return current_impl->create (initial, total); } +/* Return true if the progress gauge is "interactive", i.e. if it can + profit from being called regularly even in absence of data. The + progress bar is interactive because it regularly updates the ETA + and current update. */ + +bool +progress_interactive_p (void *progress) +{ + return current_impl->interactive; +} + /* Inform the progress gauge of newly received bytes. DLTIME is the time in milliseconds since the beginning of the download. */ void -progress_update (void *progress, long howmuch, long dltime) +progress_update (void *progress, wgint howmuch, double dltime) { current_impl->update (progress, howmuch, dltime); } @@ -171,7 +187,7 @@ progress_update (void *progress, long howmuch, long dltime) PROGRESS object, the further use of which is not allowed. */ void -progress_finish (void *progress, long dltime) +progress_finish (void *progress, double dltime) { current_impl->finish (progress, dltime); } @@ -179,37 +195,34 @@ progress_finish (void *progress, long dltime) /* Dot-printing. */ struct dot_progress { - long initial_length; /* how many bytes have been downloaded + wgint initial_length; /* how many bytes have been downloaded previously. */ - long total_length; /* expected total byte count when the + wgint total_length; /* expected total byte count when the download finishes */ int accumulated; int rows; /* number of rows printed so far */ int dots; /* number of dots printed in this row */ - long last_timer_value; + double last_timer_value; }; /* Dot-progress backend for progress_create. */ static void * -dot_create (long initial, long total) +dot_create (wgint initial, wgint total) { - struct dot_progress *dp = xmalloc (sizeof (struct dot_progress)); - - memset (dp, 0, sizeof (*dp)); - + struct dot_progress *dp = xnew0 (struct dot_progress); dp->initial_length = initial; dp->total_length = total; if (dp->initial_length) { int dot_bytes = opt.dot_bytes; - long row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint row_bytes = opt.dot_bytes * opt.dots_in_line; int remainder = (int) (dp->initial_length % row_bytes); - long skipped = dp->initial_length - remainder; + wgint skipped = dp->initial_length - remainder; if (skipped) { @@ -225,7 +238,7 @@ dot_create (long initial, long total) 2 + skipped_k_len, "", skipped_k); } - logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024); + logprintf (LOG_VERBOSE, "\n%5ldK", (long) (skipped / 1024)); for (; remainder >= dot_bytes; remainder -= dot_bytes) { if (dp->dots % opt.dot_spacing == 0) @@ -243,36 +256,42 @@ dot_create (long initial, long total) } static void -print_percentage (long bytes, long expected) +print_percentage (wgint bytes, wgint expected) { - int percentage = (int)(100.0 * bytes / expected); + /* This intentionally rounds to the floor value because it is a + measure of how much data *has* been retrieved. Therefore 12.8% + rounds to 12% because the 13% mark has not yet been reached. + Likewise, 100% is only shown when all data has been retrieved, + not before. */ + + int percentage = 100.0 * bytes / expected; logprintf (LOG_VERBOSE, "%3d%%", percentage); } static void -print_download_speed (struct dot_progress *dp, long bytes, long dltime) +print_download_speed (struct dot_progress *dp, wgint bytes, double dltime) { - logprintf (LOG_VERBOSE, " %s", - retr_rate (bytes, dltime - dp->last_timer_value, 1)); + logprintf (LOG_VERBOSE, " %7s", + retr_rate (bytes, dltime - dp->last_timer_value)); dp->last_timer_value = dltime; } /* Dot-progress backend for progress_update. */ static void -dot_update (void *progress, long howmuch, long dltime) +dot_update (void *progress, wgint howmuch, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; - long row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint row_bytes = opt.dot_bytes * opt.dots_in_line; - log_set_flush (0); + log_set_flush (false); dp->accumulated += howmuch; for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes) { if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024); + logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); if (dp->dots % opt.dot_spacing == 0) logputs (LOG_VERBOSE, " "); @@ -281,7 +300,7 @@ dot_update (void *progress, long howmuch, long dltime) ++dp->dots; if (dp->dots >= opt.dots_in_line) { - long row_qty = row_bytes; + wgint row_qty = row_bytes; if (dp->rows == dp->initial_length / row_bytes) row_qty -= dp->initial_length % row_bytes; @@ -294,23 +313,23 @@ dot_update (void *progress, long howmuch, long dltime) } } - log_set_flush (1); + log_set_flush (true); } /* Dot-progress backend for progress_finish. */ static void -dot_finish (void *progress, long dltime) +dot_finish (void *progress, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; - long row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint row_bytes = opt.dot_bytes * opt.dots_in_line; int i; - log_set_flush (0); + log_set_flush (false); if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024); + logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); for (i = dp->dots; i < opt.dots_in_line; i++) { if (i % opt.dot_spacing == 0) @@ -326,14 +345,14 @@ dot_finish (void *progress, long dltime) } { - long row_qty = dp->dots * dot_bytes + dp->accumulated; + 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); } logputs (LOG_VERBOSE, "\n\n"); - log_set_flush (0); + log_set_flush (false); xfree (dp); } @@ -401,16 +420,38 @@ dot_set_params (const char *params) create_image will overflow the buffer. */ #define MINIMUM_SCREEN_WIDTH 45 -static int screen_width = DEFAULT_SCREEN_WIDTH; +/* The last known screen width. This can be updated by the code that + detects that SIGWINCH was received (but it's never updated from the + signal handler). */ +static int screen_width; + +/* A flag that, when set, means SIGWINCH was received. */ +static volatile sig_atomic_t received_sigwinch; + +/* Size of the download speed history ring. */ +#define DLSPEED_HISTORY_SIZE 20 + +/* The minimum time length of a history sample. By default, each + sample is at least 150ms long, which means that, over the course of + 20 samples, "current" download speed spans at least 3s into the + past. */ +#define DLSPEED_SAMPLE_MIN 150 + +/* The time after which the download starts to be considered + "stalled", i.e. the current bandwidth is not printed and the recent + download speeds are scratched. */ +#define STALL_START_TIME 5000 struct bar_progress { - long initial_length; /* how many bytes have been downloaded + wgint initial_length; /* how many bytes have been downloaded previously. */ - long total_length; /* expected total byte count when the + wgint total_length; /* expected total byte count when the download finishes */ - long count; /* bytes downloaded so far */ + wgint count; /* bytes downloaded so far */ - long last_update; /* time of the last screen update. */ + double last_screen_update; /* time of the last screen update, + measured since the beginning of + download. */ int width; /* screen width we're using at the time the progress gauge was @@ -420,22 +461,68 @@ struct bar_progress { signal. */ char *buffer; /* buffer where the bar "image" is stored. */ - int tick; + int tick; /* counter used for drawing the + progress bar where the total size + is not known. */ + + /* The following variables (kept in a struct for namespace reasons) + keep track of recent download speeds. See bar_update() for + details. */ + struct bar_progress_hist { + int pos; + wgint times[DLSPEED_HISTORY_SIZE]; + wgint bytes[DLSPEED_HISTORY_SIZE]; + + /* The sum of times and bytes respectively, maintained for + efficiency. */ + wgint total_time; + wgint total_bytes; + } hist; + + double recent_start; /* timestamp of beginning of current + position. */ + wgint recent_bytes; /* bytes downloaded so far. */ + + bool stalled; /* set when no data arrives for longer + than STALL_START_TIME, then reset + when new data arrives. */ + + /* create_image() uses these to make sure that ETA information + doesn't flicker. */ + double last_eta_time; /* time of the last update to download + speed and ETA, measured since the + beginning of download. */ + int last_eta_value; }; -static void create_image PARAMS ((struct bar_progress *, long)); -static void display_image PARAMS ((char *)); +static void create_image (struct bar_progress *, double, bool); +static void display_image (char *); static void * -bar_create (long initial, long total) +bar_create (wgint initial, wgint total) { - struct bar_progress *bp = xmalloc (sizeof (struct bar_progress)); + struct bar_progress *bp = xnew0 (struct bar_progress); - memset (bp, 0, sizeof (*bp)); + /* In theory, our callers should take care of this pathological + case, but it can sometimes happen. */ + if (initial > total) + total = initial; bp->initial_length = initial; bp->total_length = total; + /* Initialize screen_width if this hasn't been done or if it might + have changed, as indicated by receiving SIGWINCH. */ + if (!screen_width || received_sigwinch) + { + screen_width = determine_screen_width (); + if (!screen_width) + screen_width = DEFAULT_SCREEN_WIDTH; + else if (screen_width < MINIMUM_SCREEN_WIDTH) + screen_width = MINIMUM_SCREEN_WIDTH; + received_sigwinch = 0; + } + /* - 1 because we don't want to use the last screen column. */ bp->width = screen_width - 1; /* + 1 for the terminating zero. */ @@ -443,17 +530,19 @@ bar_create (long initial, long total) logputs (LOG_VERBOSE, "\n"); - create_image (bp, 0); + create_image (bp, 0, false); display_image (bp->buffer); return bp; } +static void update_speed_ring (struct bar_progress *, wgint, double); + static void -bar_update (void *progress, long howmuch, long dltime) +bar_update (void *progress, wgint howmuch, double dltime) { struct bar_progress *bp = progress; - int force_update = 0; + bool force_screen_update = false; bp->count += howmuch; if (bp->total_length > 0 @@ -463,37 +552,49 @@ bar_update (void *progress, long howmuch, long dltime) adjust bp->total_length to the new reality, so that the code in create_image() that depends on total size being smaller or equal to the expected size doesn't abort. */ - bp->total_length = bp->count + bp->initial_length; + bp->total_length = bp->initial_length + bp->count; + + update_speed_ring (bp, howmuch, dltime); - if (screen_width - 1 != bp->width) + /* If SIGWINCH (the window size change signal) been received, + determine the new screen size and update the screen. */ + if (received_sigwinch) { - bp->width = screen_width - 1; - bp->buffer = xrealloc (bp->buffer, bp->width + 1); - force_update = 1; + int old_width = screen_width; + screen_width = determine_screen_width (); + if (!screen_width) + screen_width = DEFAULT_SCREEN_WIDTH; + else if (screen_width < MINIMUM_SCREEN_WIDTH) + screen_width = MINIMUM_SCREEN_WIDTH; + if (screen_width != old_width) + { + bp->width = screen_width - 1; + bp->buffer = xrealloc (bp->buffer, bp->width + 1); + force_screen_update = true; + } + received_sigwinch = 0; } - if (dltime - bp->last_update < 200 && !force_update) + if (dltime - bp->last_screen_update < 200 && !force_screen_update) /* Don't update more often than five times per second. */ return; - bp->last_update = dltime; - - create_image (bp, dltime); + create_image (bp, dltime, false); display_image (bp->buffer); + bp->last_screen_update = dltime; } static void -bar_finish (void *progress, long dltime) +bar_finish (void *progress, double dltime) { struct bar_progress *bp = progress; - if (dltime == 0) - /* If the download was faster than the granularity of the timer, - fake some output so that we don't get the ugly "----.--" rate - at the download finish. */ - dltime = 1; + if (bp->total_length > 0 + && bp->count + bp->initial_length > bp->total_length) + /* 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"); @@ -502,26 +603,138 @@ bar_finish (void *progress, long dltime) xfree (bp); } +/* This code attempts to maintain the notion of a "current" download + speed, over the course of no less than 3s. (Shorter intervals + produce very erratic results.) + + To do so, it samples the speed in 150ms intervals and stores the + recorded samples in a FIFO history ring. The ring stores no more + than 20 intervals, hence the history covers the period of at least + three seconds and at most 20 reads into the past. This method + should produce reasonable results for downloads ranging from very + slow to very fast. + + The idea is that for fast downloads, we get the speed over exactly + the last three seconds. For slow downloads (where a network read + takes more than 150ms to complete), we get the speed over a larger + time period, as large as it takes to complete thirty reads. This + is good because slow downloads tend to fluctuate more and a + 3-second average would be too erratic. */ + +static void +update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime) +{ + struct bar_progress_hist *hist = &bp->hist; + double recent_age = dltime - bp->recent_start; + + /* Update the download count. */ + bp->recent_bytes += howmuch; + + /* For very small time intervals, we return after having updated the + "recent" download count. When its age reaches or exceeds minimum + sample time, it will be recorded in the history ring. */ + if (recent_age < DLSPEED_SAMPLE_MIN) + return; + + if (howmuch == 0) + { + /* If we're not downloading anything, we might be stalling, + i.e. not downloading anything for an extended period of time. + Since 0-reads do not enter the history ring, recent_age + effectively measures the time since last read. */ + if (recent_age >= STALL_START_TIME) + { + /* If we're stalling, reset the ring contents because it's + stale and because it will make bar_update stop printing + the (bogus) current bandwidth. */ + bp->stalled = true; + xzero (*hist); + bp->recent_bytes = 0; + } + return; + } + + /* We now have a non-zero amount of to store to the speed ring. */ + + /* If the stall status was acquired, reset it. */ + if (bp->stalled) + { + bp->stalled = false; + /* "recent_age" includes the the entired stalled period, which + could be very long. Don't update the speed ring with that + value because the current bandwidth would start too small. + Start with an arbitrary (but more reasonable) time value and + let it level out. */ + recent_age = 1000; + } + + /* Store "recent" bytes and download time to history ring at the + position POS. */ + + /* To correctly maintain the totals, first invalidate existing data + (least recent in time) at this position. */ + hist->total_time -= hist->times[hist->pos]; + hist->total_bytes -= hist->bytes[hist->pos]; + + /* Now store the new data and update the totals. */ + hist->times[hist->pos] = recent_age; + hist->bytes[hist->pos] = bp->recent_bytes; + hist->total_time += recent_age; + hist->total_bytes += bp->recent_bytes; + + /* Start a new "recent" period. */ + bp->recent_start = dltime; + bp->recent_bytes = 0; + + /* Advance the current ring position. */ + if (++hist->pos == DLSPEED_HISTORY_SIZE) + hist->pos = 0; + +#if 0 + /* Sledgehammer check to verify that the totals are accurate. */ + { + int i; + double sumt = 0, sumb = 0; + for (i = 0; i < DLSPEED_HISTORY_SIZE; i++) + { + sumt += hist->times[i]; + sumb += hist->bytes[i]; + } + assert (sumt == hist->total_time); + assert (sumb == hist->total_bytes); + } +#endif +} + +static const char *eta_to_human_short (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, long dltime) +create_image (struct bar_progress *bp, double dl_total_time, bool done) { char *p = bp->buffer; - long size = bp->initial_length + bp->count; + wgint size = bp->initial_length + bp->count; - char *size_legible = legible (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; /* The progress bar should look like this: - xx% [=======> ] nn,nnn 12.34K/s ETA 00:00 + xx% [=======> ] nn,nnn 12.34K/s eta 36m 51s Calculate the geometry. The idea is to assign as much room as possible to the progress bar. The other idea is to never let @@ -534,11 +747,11 @@ create_image (struct bar_progress *bp, long dltime) "[]" - progress bar decorations - 2 chars " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more " 1012.56K/s" - dl rate - 11 chars - " ETA xx:xx:xx" - ETA - 13 chars + " eta 36m 51s" - ETA - 13 chars "=====>..." - progress bar - the rest */ - int dlbytes_size = 1 + MAX (size_legible_len, 11); + int dlbytes_size = 1 + MAX (size_grouped_len, 11); int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13); if (progress_size < 5) @@ -547,8 +760,7 @@ create_image (struct bar_progress *bp, long dltime) /* "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) @@ -560,29 +772,38 @@ create_image (struct bar_progress *bp, long dltime) else APPEND_LITERAL (" "); - /* The progress bar: "[====> ]" */ + /* The progress bar: "[====> ]" or "[++==> ]". */ if (progress_size && bp->total_length > 0) { - double fraction = (double)size / bp->total_length; - int dlsz = (int)(fraction * progress_size); + /* Size of the initial portion. */ + int insz = (double)bp->initial_length / bp->total_length * progress_size; + + /* Size of the downloaded portion. */ + int dlsz = (double)size / bp->total_length * progress_size; + char *begin; + int i; assert (dlsz <= progress_size); + assert (insz <= dlsz); *p++ = '['; begin = p; + /* Print the initial portion of the download with '+' chars, the + rest with '=' and one '>'. */ + for (i = 0; i < insz; i++) + *p++ = '+'; + dlsz -= insz; if (dlsz > 0) { - /* Draw dlsz-1 '=' chars and one arrow char. */ - while (dlsz-- > 1) + for (i = 0; i < dlsz - 1; i++) *p++ = '='; *p++ = '>'; } while (p - begin < progress_size) *p++ = ' '; - *p++ = ']'; } else if (progress_size) @@ -613,62 +834,86 @@ create_image (struct bar_progress *bp, long dltime) } /* " 234,567,890" */ - sprintf (p, " %-11s", legible (size)); - p += strlen (p); + sprintf (p, " %-11s", size_grouped); + move_to_end (p); /* " 1012.45K/s" */ - if (dltime && bp->count) + if (hist->total_time && hist->total_bytes) { - static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" }; + static const char *short_units[] = { "B/s", "K/s", "M/s", "G/s" }; int units = 0; - double dlrate = calc_rate (bp->count, dltime, &units); - sprintf (p, " %7.2f%s", dlrate, short_units[units]); - p += strlen (p); + /* Calculate the download speed using the history ring and + recent data that hasn't made it to the ring yet. */ + 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]); + move_to_end (p); } else APPEND_LITERAL (" --.--K/s"); - /* " ETA xx:xx:xx" */ - if (bp->total_length > 0 && bp->count > 0) + if (!done) { - int eta, eta_hrs, eta_min, eta_sec; - double tm_sofar = (double)dltime / 1000; - long bytes_remaining = bp->total_length - size; - - eta = (int) (tm_sofar * bytes_remaining / bp->count); - - eta_hrs = eta / 3600, eta %= 3600; - eta_min = eta / 60, eta %= 60; - eta_sec = eta; - - /* Pad until the end of screen. The padding is dependent on the - hour value. */ - if (eta_hrs == 0 || eta_hrs > 99) - /* Hours not printed: pad with three spaces (two digits and - colon). */ - APPEND_LITERAL (" "); - else if (eta_hrs >= 10) - /* Hours printed with one digit: pad with one space. */ - *p++ = ' '; - else - /* Hours printed with two digits: we're using maximum width, - don't pad. */ - ; - - APPEND_LITERAL (" ETA "); - - if (eta_hrs > 99) - /* Bogus value, probably due to a calculation overflow. Print - something safe to avoid overstepping the buffer bounds. */ - sprintf (p, "--:--"); - else if (eta_hrs > 0) - sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec); + /* " 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) + { + 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)); + move_to_end (p); + } + else if (bp->total_length > 0) + { + APPEND_LITERAL (" "); + } + } + else + { + /* 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))); else - sprintf (p, "%02d:%02d", eta_min, eta_sec); - p += strlen (p); + /* For very quick downloads show more exact timing information. */ + sprintf (p, "%.*fs", + secs < 0.001 ? 0 : /* 0s instead of 0.000s */ + secs < 0.01 ? 3 : /* 0.00x */ + secs < 0.1 ? 2 : /* 0.0x */ + 1, /* 0.x, 1.x, ..., 9.x */ + secs); + move_to_end (p); } - else if (bp->total_length > 0) - APPEND_LITERAL (" ETA --:--"); assert (p - bp->buffer <= bp->width); @@ -683,14 +928,16 @@ create_image (struct bar_progress *bp, long dltime) static void display_image (char *buf) { + bool old = log_set_save_context (false); logputs (LOG_VERBOSE, "\r"); logputs (LOG_VERBOSE, buf); + log_set_save_context (old); } static void bar_set_params (const char *params) { - int sw; + char *term = getenv ("TERM"); if (params && 0 == strcmp (params, "force")) @@ -698,10 +945,17 @@ bar_set_params (const char *params) if ((opt.lfilename #ifdef HAVE_ISATTY + /* The progress bar doesn't make sense if the output is not a + TTY -- when logging to file, it is better to review the + dots. */ || !isatty (fileno (stderr)) -#else - 1 #endif + /* Normally we don't depend on terminal type because the + progress bar only uses ^M to move the cursor to the + beginning of line, which works even on dumb terminals. But + Jamie Zawinski reports that ^M and ^H tricks don't work in + Emacs shell buffers, and only make a mess. */ + || (term && 0 == strcmp (term, "emacs")) ) && !current_impl_locked) { @@ -713,19 +967,56 @@ bar_set_params (const char *params) set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION); return; } - - sw = determine_screen_width (); - if (sw && sw >= MINIMUM_SCREEN_WIDTH) - screen_width = sw; } #ifdef SIGWINCH -RETSIGTYPE +void progress_handle_sigwinch (int sig) { - int sw = determine_screen_width (); - if (sw && sw >= MINIMUM_SCREEN_WIDTH) - screen_width = sw; + received_sigwinch = 1; signal (SIGWINCH, progress_handle_sigwinch); } #endif + +/* 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 SEP is false, 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_short (int secs) +{ + static char buf[10]; /* 8 should be enough, but just in case */ + static int last = -1; + + /* 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); + else if (secs < 100 * 3600) + sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60); + else if (secs < 100 * 86400) + sprintf (buf, "%dd %dh", secs / 86400, (secs / 3600) % 60); + else + /* even (2^31-1)/86400 doesn't overflow BUF. */ + sprintf (buf, "%dd", secs / 86400); + + return buf; +}