#include <stdio.h>
#include <stdlib.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif /* HAVE_STRING_H */
+#include <string.h>
#include <assert.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
-#ifdef HAVE_SIGNAL_H
-# include <signal.h>
-#endif
+#include <signal.h>
#include "wget.h"
#include "progress.h"
#include "retr.h"
struct progress_implementation {
- char *name;
- void *(*create) PARAMS ((long, long));
- void (*update) PARAMS ((void *, long, long));
- void (*finish) PARAMS ((void *, long));
- void (*set_params) PARAMS ((const char *));
+ 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;
#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
#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. */
{
int i, namelen;
struct progress_implementation *pi = implementations;
- char *colon;
+ const char *colon;
if (!name)
name = DEFAULT_PROGRESS_IMPLEMENTATION;
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;
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)
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);
}
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);
}
/* 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)
{
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)
}
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, " ");
++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;
}
}
- 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)
}
{
- 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);
}
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 history table for download speeds. */
-#define DLSPEED_HISTORY_SIZE 30
+/* Size of the download speed history ring. */
+#define DLSPEED_HISTORY_SIZE 20
-/* The time interval in milliseconds below which we increase old
- history entries rather than overwriting them. That interval
- represents the scope of the download speed history. */
-#define DLSPEED_HISTORY_MAX_INTERVAL 3000
+/* 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_screen_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
details. */
struct bar_progress_hist {
int pos;
- long times[DLSPEED_HISTORY_SIZE];
- long bytes[DLSPEED_HISTORY_SIZE];
- long summed_times;
- long summed_bytes;
- long previous_time;
+ 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 flash. */
- long last_eta_time; /* time of the last update to download
- speed and ETA. */
- long last_eta_value;
+ 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));
-
- memset (bp, 0, sizeof (*bp));
+ struct bar_progress *bp = xnew0 (struct bar_progress);
/* In theory, our callers should take care of this pathological
case, but it can sometimes happen. */
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. */
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;
- struct bar_progress_hist *hist = &bp->hist;
- int force_screen_update = 0;
- long delta_time = dltime - hist->previous_time;
+ bool force_screen_update = false;
bp->count += howmuch;
if (bp->total_length > 0
equal to the expected size doesn't abort. */
bp->total_length = bp->initial_length + bp->count;
- /* This code attempts to determine the current download speed. We
- measure the speed over the interval of approximately three
- seconds, in subintervals no smaller than 0.1s. In other words,
- we maintain and use the history of 30 most recent reads, where a
- "read" consists of one or more network reads, up until the point
- where a subinterval is filled. */
-
- if (hist->times[hist->pos]
- >= DLSPEED_HISTORY_MAX_INTERVAL / DLSPEED_HISTORY_SIZE)
- {
- /* The subinterval at POS has been used up. Move on to the next
- position. */
- if (++hist->pos == DLSPEED_HISTORY_SIZE)
- hist->pos = 0;
-
- /* Invalidate old data (from the previous cycle) at this
- position. */
- hist->summed_times -= hist->times[hist->pos];
- hist->summed_bytes -= hist->bytes[hist->pos];
- hist->times[hist->pos] = delta_time;
- hist->bytes[hist->pos] = howmuch;
- }
- else
- {
- /* Increment the data at POS. */
- hist->times[hist->pos] += delta_time;
- hist->bytes[hist->pos] += howmuch;
- }
-
- hist->summed_times += delta_time;
- hist->summed_bytes += howmuch;
- hist->previous_time = dltime;
+ update_speed_ring (bp, howmuch, dltime);
-#if 0
- /* Sledgehammer check that summed_times and summed_bytes are
- accurate. */
- {
- int i;
- long sumt = 0, sumb = 0;
- for (i = 0; i < DLSPEED_HISTORY_SIZE; i++)
- {
- sumt += hist->times[i];
- sumb += hist->bytes[i];
- }
- assert (sumt == hist->summed_times);
- assert (sumb == hist->summed_bytes);
- }
-#endif
-
- 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_screen_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_screen_update < 200 && !force_screen_update)
/* 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;
}
static void
-bar_finish (void *progress, long dltime)
+bar_finish (void *progress, double dltime)
{
struct bar_progress *bp = progress;
/* 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");
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 dl_total_time)
+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
"[]" - 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)
/* "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)
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)
}
/* " 234,567,890" */
- sprintf (p, " %-11s", legible (size));
- p += strlen (p);
+ sprintf (p, " %-11s", size_grouped);
+ move_to_end (p);
/* " 1012.45K/s" */
- if (hist->summed_times && hist->summed_bytes)
+ 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;
- dlrate = calc_rate (hist->summed_bytes, hist->summed_times, &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 && dl_total_time > 3000)
+ if (!done)
{
- long eta;
- int eta_hrs, eta_min, eta_sec;
-
- /* 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 (dl_total_time - bp->last_eta_time < 900
- && bp->last_eta_value != 0)
- 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 the current speed
- instead, replace dl_total_time with hist->summed_times
- and bp->count with hist->summed_bytes. I found that
- doing that results in a very jerky and ultimately
- unreliable ETA. */
- double time_sofar = (double)dl_total_time / 1000;
- long bytes_remaining = bp->total_length - size;
- eta = (long) (time_sofar * bytes_remaining / bp->count);
- bp->last_eta_value = eta;
- bp->last_eta_time = dl_total_time;
- }
-
- eta_hrs = eta / 3600, eta %= 3600;
- eta_min = eta / 60, eta %= 60;
- eta_sec = eta;
-
- if (eta_hrs > 99)
- goto no_eta;
-
- if (eta_hrs == 0)
- {
- /* Hours not printed: pad with three spaces. */
- APPEND_LITERAL (" ");
- sprintf (p, " ETA %02d:%02d", eta_min, eta_sec);
+ 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
+ else if (bp->total_length > 0)
{
- if (eta_hrs < 10)
- /* Hours printed with one digit: pad with one space. */
- *p++ = ' ';
- sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec);
+ APPEND_LITERAL (" ");
}
- p += strlen (p);
}
- else if (bp->total_length > 0)
+ else
{
- no_eta:
- 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)));
+ else
+ /* 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);
}
assert (p - bp->buffer <= bp->width);
static void
display_image (char *buf)
{
- int old = log_set_save_context (0);
+ 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"))
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)
{
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;
+}