summaryrefslogtreecommitdiffstats
path: root/src/progress.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/progress.c1279
1 files changed, 1279 insertions, 0 deletions
diff --git a/src/progress.c b/src/progress.c
new file mode 100644
index 0000000..3d8ba53
--- /dev/null
+++ b/src/progress.c
@@ -0,0 +1,1279 @@
+/* Download progress.
+ Copyright (C) 2001-2011, 2015, 2018 Free Software Foundation, Inc.
+
+This file is part of GNU Wget.
+
+GNU Wget is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+GNU Wget is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Wget. If not, see <http://www.gnu.org/licenses/>.
+
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work. */
+
+#include "wget.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+#include <wchar.h>
+#include <mbiter.h>
+
+#include "progress.h"
+#include "utils.h"
+#include "retr.h"
+#include "c-strcase.h"
+
+struct progress_implementation {
+ const char *name;
+ bool interactive;
+ void *(*create) (const char *, wgint, wgint);
+ void (*update) (void *, wgint, double);
+ void (*draw) (void *);
+ void (*finish) (void *, double);
+ void (*set_params) (char *);
+};
+
+/* Necessary forward declarations. */
+
+static void *dot_create (const char *, wgint, wgint);
+static void dot_update (void *, wgint, double);
+static void dot_finish (void *, double);
+static void dot_draw (void *);
+static void dot_set_params (char *);
+
+static void *bar_create (const char *, wgint, wgint);
+static void bar_update (void *, wgint, double);
+static void bar_draw (void *);
+static void bar_finish (void *, double);
+static void bar_set_params (char *);
+
+static struct progress_implementation implementations[] = {
+ { "dot", 0, dot_create, dot_update, dot_draw, dot_finish, dot_set_params },
+ { "bar", 1, bar_create, bar_update, bar_draw, bar_finish, bar_set_params }
+};
+static struct progress_implementation *current_impl;
+static int current_impl_locked;
+
+/* Progress implementation used by default. Can be overridden in
+ wgetrc or by the fallback one. */
+
+#define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
+
+/* 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
+ infloop. */
+
+#define FALLBACK_PROGRESS_IMPLEMENTATION "dot"
+
+/* Return true if NAME names a valid progress bar implementation. The
+ characters after the first : will be ignored. */
+
+bool
+valid_progress_implementation_p (const char *name)
+{
+ size_t i;
+ struct progress_implementation *pi = implementations;
+ char *colon = strchr (name, ':');
+ size_t namelen = colon ? (size_t) (colon - name) : strlen (name);
+
+ for (i = 0; i < countof (implementations); i++, pi++)
+ if (!strncmp (pi->name, name, namelen))
+ return true;
+ return false;
+}
+
+/* Set the progress implementation to NAME. */
+
+void
+set_progress_implementation (const char *name)
+{
+ size_t i, namelen;
+ struct progress_implementation *pi = implementations;
+ char *colon;
+
+ if (!name)
+ name = DEFAULT_PROGRESS_IMPLEMENTATION;
+
+ colon = strchr (name, ':');
+ namelen = colon ? (size_t) (colon - name) : strlen (name);
+
+ for (i = 0; i < countof (implementations); i++, pi++)
+ if (!strncmp (pi->name, name, namelen))
+ {
+ current_impl = pi;
+ current_impl_locked = 0;
+
+ if (colon)
+ /* We call pi->set_params even if colon is NULL because we
+ want to give the implementation a chance to set up some
+ things it needs to run. */
+ ++colon;
+
+ if (pi->set_params)
+ pi->set_params (colon);
+ return;
+ }
+ abort ();
+}
+
+static int output_redirected;
+
+void
+progress_schedule_redirect (void)
+{
+ output_redirected = 1;
+}
+
+/* Create a progress gauge. INITIAL is the number of bytes the
+ download starts from (zero if the download starts from scratch).
+ TOTAL is the expected total number of bytes in this download. If
+ TOTAL is zero, it means that the download size is not known in
+ advance. */
+
+void *
+progress_create (const char *f_download, wgint initial, wgint total)
+{
+ /* Check if the log status has changed under our feet. */
+ if (output_redirected)
+ {
+ if (!current_impl_locked)
+ set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
+ output_redirected = 0;
+ }
+
+ return current_impl->create (f_download, 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 _GL_UNUSED)
+{
+ return current_impl->interactive;
+}
+
+/* Inform the progress gauge of newly received bytes. DLTIME is the
+ time since the beginning of the download. */
+
+void
+progress_update (void *progress, wgint howmuch, double dltime)
+{
+ current_impl->update (progress, howmuch, dltime);
+ current_impl->draw (progress);
+}
+
+/* Tell the progress gauge to clean up. Calling this will free the
+ PROGRESS object, the further use of which is not allowed. */
+
+void
+progress_finish (void *progress, double dltime)
+{
+ current_impl->finish (progress, dltime);
+}
+
+/* Dot-printing. */
+
+struct dot_progress {
+ wgint initial_length; /* how many bytes have been downloaded
+ previously. */
+ wgint total_length; /* expected total byte count when the
+ download finishes */
+
+ int accumulated; /* number of bytes accumulated after
+ the last printed dot */
+
+ double dltime; /* download time so far */
+ int rows; /* number of rows printed so far */
+ int dots; /* number of dots printed in this row */
+
+ double last_timer_value;
+};
+
+/* Dot-progress backend for progress_create. */
+
+static void *
+dot_create (const char *f_download _GL_UNUSED, wgint initial, wgint total)
+{
+ 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;
+ const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
+
+ int remainder = dp->initial_length % ROW_BYTES;
+ wgint skipped = dp->initial_length - remainder;
+
+ if (skipped)
+ {
+ wgint skipped_k = skipped / 1024; /* skipped amount in K */
+ int skipped_k_len = numdigit (skipped_k);
+ 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_PROGRESS, _("\n%*s[ skipping %sK ]"),
+ 2 + skipped_k_len, "",
+ number_to_static_string (skipped_k));
+ }
+
+ logprintf (LOG_PROGRESS, "\n%6sK",
+ number_to_static_string (skipped / 1024));
+ for (; remainder >= dot_bytes; remainder -= dot_bytes)
+ {
+ if (dp->dots % opt.dot_spacing == 0)
+ logputs (LOG_PROGRESS, " ");
+ logputs (LOG_PROGRESS, ",");
+ ++dp->dots;
+ }
+ assert (dp->dots < opt.dots_in_line);
+
+ dp->accumulated = remainder;
+ dp->rows = skipped / ROW_BYTES;
+ }
+
+ return dp;
+}
+
+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_row_stats (struct dot_progress *dp, double dltime, bool last)
+{
+ 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_PROGRESS, "%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_PROGRESS, " %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 eta = dltime * bytes_remaining / bytes_sofar;
+ if (eta < INT_MAX - 1)
+ logprintf (LOG_PROGRESS, " %s",
+ eta_to_human_short ((int) (eta + 0.5), true));
+ }
+ }
+ else
+ {
+ /* When done, print the total download time */
+ if (dltime >= 10)
+ logprintf (LOG_PROGRESS, "=%s",
+ eta_to_human_short ((int) (dltime + 0.5), true));
+ else
+ logprintf (LOG_PROGRESS, "=%ss", print_decimal (dltime));
+ }
+}
+
+/* Dot-progress backend for progress_update. */
+
+static void
+dot_update (void *progress, wgint howmuch, double dltime)
+{
+ struct dot_progress *dp = progress;
+ dp->accumulated += howmuch;
+ dp->dltime = dltime;
+}
+
+static void
+dot_draw (void *progress)
+{
+ struct dot_progress *dp = progress;
+ int dot_bytes = opt.dot_bytes;
+ 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_PROGRESS, "\n%6sK",
+ number_to_static_string (dp->rows * ROW_BYTES / 1024));
+
+ if (dp->dots % opt.dot_spacing == 0)
+ logputs (LOG_PROGRESS, " ");
+ logputs (LOG_PROGRESS, ".");
+
+ ++dp->dots;
+ if (dp->dots >= opt.dots_in_line)
+ {
+ ++dp->rows;
+ dp->dots = 0;
+
+ print_row_stats (dp, dp->dltime, false);
+ }
+ }
+
+ log_set_flush (true);
+}
+
+/* Dot-progress backend for progress_finish. */
+
+static void
+dot_finish (void *progress, double dltime)
+{
+ struct dot_progress *dp = progress;
+ wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line;
+ int i;
+
+ log_set_flush (false);
+
+ if (dp->dots == 0)
+ logprintf (LOG_PROGRESS, "\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_PROGRESS, " ");
+ logputs (LOG_PROGRESS, " ");
+ }
+
+ print_row_stats (dp, dltime, true);
+ logputs (LOG_VERBOSE, "\n\n");
+ log_set_flush (false);
+
+ xfree (dp);
+}
+
+/* This function interprets the progress "parameters". For example,
+ if Wget is invoked with --progress=dot:mega, it will set the
+ "dot-style" to "mega". Valid styles are default, binary, mega, and
+ giga. */
+
+static void
+dot_set_params (char *params)
+{
+ if (!params || !*params)
+ params = opt.dot_style;
+
+ if (!params)
+ return;
+
+ /* We use this to set the retrieval style. */
+ if (!c_strcasecmp (params, "default"))
+ {
+ /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
+ line. */
+ opt.dot_bytes = 1024;
+ opt.dot_spacing = 10;
+ opt.dots_in_line = 50;
+ }
+ else if (!c_strcasecmp (params, "binary"))
+ {
+ /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
+ (384K) in a line. */
+ opt.dot_bytes = 8192;
+ opt.dot_spacing = 16;
+ opt.dots_in_line = 48;
+ }
+ else if (!c_strcasecmp (params, "mega"))
+ {
+ /* "Mega" retrieval, for retrieving very long files; each dot is
+ 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
+ opt.dot_bytes = 65536L;
+ opt.dot_spacing = 8;
+ opt.dots_in_line = 48;
+ }
+ else if (!c_strcasecmp (params, "giga"))
+ {
+ /* "Giga" retrieval, for retrieving very very *very* long files;
+ each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
+ line. */
+ opt.dot_bytes = (1L << 20);
+ opt.dot_spacing = 8;
+ opt.dots_in_line = 32;
+ }
+ else
+ fprintf (stderr,
+ _("Invalid dot style specification %s; leaving unchanged.\n"),
+ quote (params));
+}
+
+/* "Thermometer" (bar) progress. */
+
+/* Assumed screen width if we can't find the real value. */
+#define DEFAULT_SCREEN_WIDTH 80
+
+/* Minimum screen width we'll try to work with. If this is too small,
+ create_image will overflow the buffer. */
+#define MINIMUM_SCREEN_WIDTH 45
+
+/* 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 0.15
+
+/* 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 5
+
+/* Time between screen refreshes will not be shorter than this, so
+ that Wget doesn't swamp the TTY with output. */
+#define REFRESH_INTERVAL 0.2
+
+/* Don't refresh the ETA too often to avoid jerkiness in predictions.
+ This allows ETA to change approximately once per second. */
+#define ETA_REFRESH_INTERVAL 0.99
+
+struct bar_progress {
+ const char *f_download; /* Filename of the downloaded file */
+ wgint initial_length; /* how many bytes have been downloaded
+ previously. */
+ wgint total_length; /* expected total byte count when the
+ download finishes */
+ wgint count; /* bytes downloaded so far */
+
+ double last_screen_update; /* time of the last screen update,
+ measured since the beginning of
+ download. */
+
+ double dltime; /* download time so far */
+ int width; /* screen width we're using at the
+ time the progress gauge was
+ created. this is different from
+ the screen_width global variable in
+ that the latter can be changed by a
+ signal. */
+ char *buffer; /* buffer where the bar "image" is
+ stored. */
+ 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;
+ double times[DLSPEED_HISTORY_SIZE];
+ wgint bytes[DLSPEED_HISTORY_SIZE];
+
+ /* The sum of times and bytes respectively, maintained for
+ efficiency. */
+ double 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 (struct bar_progress *, double, bool);
+static void display_image (char *);
+
+static void *
+bar_create (const char *f_download, wgint initial, wgint total)
+{
+ struct bar_progress *bp = xnew0 (struct bar_progress);
+
+ /* 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;
+ bp->f_download = f_download;
+
+ /* 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;
+ /* + enough space for the terminating zero, and hopefully enough room
+ * for multibyte characters. */
+#define BUF_LEN (bp->width + 100)
+ bp->buffer = xmalloc (BUF_LEN);
+
+ logputs (LOG_VERBOSE, "\n");
+
+ 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, wgint howmuch, double dltime)
+{
+ struct bar_progress *bp = progress;
+
+ bp->dltime = dltime;
+ bp->count += howmuch;
+ if (bp->total_length > 0
+ && bp->count + bp->initial_length > bp->total_length)
+ /* We could be downloading more than total_length, e.g. when the
+ server sends an incorrect Content-Length header. In that case,
+ 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->initial_length + bp->count;
+
+ update_speed_ring (bp, howmuch, dltime);
+}
+
+static void
+bar_draw (void *progress)
+{
+ bool force_screen_update = false;
+ struct bar_progress *bp = progress;
+
+ /* If SIGWINCH (the window size change signal) been received,
+ determine the new screen size and update the screen. */
+ if (received_sigwinch)
+ {
+ 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 + 100);
+ force_screen_update = true;
+ }
+ received_sigwinch = 0;
+ }
+
+ if (bp->dltime - bp->last_screen_update < REFRESH_INTERVAL && !force_screen_update)
+ /* Don't update more often than five times per second. */
+ return;
+
+ create_image (bp, bp->dltime, false);
+ display_image (bp->buffer);
+ bp->last_screen_update = bp->dltime;
+}
+
+static void
+bar_finish (void *progress, double dltime)
+{
+ struct bar_progress *bp = progress;
+
+ 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, true);
+ display_image (bp->buffer);
+
+ logputs (LOG_VERBOSE, "\n");
+ logputs (LOG_PROGRESS, "\n");
+
+ xfree (bp->buffer);
+ 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 twenty 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 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 = 1;
+ }
+
+ /* 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 (sumb == hist->total_bytes);
+ /* We can't use assert(sumt==hist->total_time) because some
+ precision is lost by adding and subtracting floating-point
+ numbers. But during a download this precision should not be
+ detectable, i.e. no larger than 1ns. */
+ double diff = sumt - hist->total_time;
+ if (diff < 0) diff = -diff;
+ assert (diff < 1e-9);
+ }
+#endif
+}
+
+#if USE_NLS_PROGRESS_BAR
+static int
+count_cols (const char *mbs)
+{
+ wchar_t wc;
+ int bytes;
+ int remaining = strlen(mbs);
+ int cols = 0;
+ int wccols;
+
+ while (*mbs != '\0')
+ {
+ bytes = mbtowc (&wc, mbs, remaining);
+ assert (bytes != 0); /* Only happens when *mbs == '\0' */
+ if (bytes == -1)
+ {
+ /* Invalid sequence. We'll just have to fudge it. */
+ return cols + remaining;
+ }
+ mbs += bytes;
+ remaining -= bytes;
+ wccols = wcwidth(wc);
+ cols += (wccols == -1? 1 : wccols);
+ }
+ return cols;
+}
+
+static int
+cols_to_bytes (const char *mbs, const int cols, int *ncols)
+{
+ int p_cols = 0, bytes = 0;
+ mbchar_t mbc;
+ mbi_iterator_t iter;
+ mbi_init (iter, mbs, strlen(mbs));
+ while (p_cols < cols && mbi_avail (iter))
+ {
+ mbc = mbi_cur (iter);
+ p_cols += mb_width (mbc);
+ /* The multibyte character has exceeded the total number of columns we
+ * have available. The remaining bytes will be padded with a space. */
+ if (p_cols > cols)
+ {
+ p_cols -= mb_width (mbc);
+ break;
+ }
+ bytes += mb_len (mbc);
+ mbi_advance (iter);
+ }
+ *ncols = p_cols;
+ return bytes;
+}
+#else
+static int count_cols (const char *mbs) { return (int) strlen(mbs); }
+static int
+cols_to_bytes (const char *mbs _GL_UNUSED, const int cols, int *ncols)
+{
+ *ncols = cols;
+ return cols;
+}
+#endif
+
+static const char *
+get_eta (int *bcd)
+{
+ /* TRANSLATORS: "ETA" is English-centric, but this must
+ be short, ideally 3 chars. Abbreviate if necessary. */
+ static const char eta_str[] = N_(" eta %s");
+ static const char *eta_trans;
+ static int bytes_cols_diff;
+ if (eta_trans == NULL)
+ {
+ int nbytes;
+ int ncols;
+
+#if USE_NLS_PROGRESS_BAR
+ eta_trans = _(eta_str);
+#else
+ eta_trans = eta_str;
+#endif
+
+ /* Determine the number of bytes used in the translated string,
+ * versus the number of columns used. This is to figure out how
+ * many spaces to add at the end to pad to the full line width.
+ *
+ * We'll store the difference between the number of bytes and
+ * number of columns, so that removing this from the string length
+ * will reveal the total number of columns in the progress bar. */
+ nbytes = strlen (eta_trans);
+ ncols = count_cols (eta_trans);
+ bytes_cols_diff = nbytes - ncols;
+ }
+
+ if (bcd != NULL)
+ *bcd = bytes_cols_diff;
+
+ return eta_trans;
+}
+
+#define APPEND_LITERAL(s) do { \
+ memcpy (p, s, sizeof (s) - 1); \
+ p += sizeof (s) - 1; \
+} while (0)
+
+static void
+create_image (struct bar_progress *bp, double dl_total_time, bool done)
+{
+ const int MAX_FILENAME_COLS = bp->width / 4;
+ char *p = bp->buffer;
+ wgint size = bp->initial_length + bp->count;
+
+ struct bar_progress_hist *hist = &bp->hist;
+ int orig_filename_cols = count_cols (bp->f_download);
+
+ int padding;
+
+ /* The progress bar should look like this:
+ file xx% [=======> ] nnn.nnK 12.34KB/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
+ things "jitter", i.e. pad elements that vary in size so that
+ their variance does not affect the placement of other elements.
+ It would be especially bad for the progress bar to be resized
+ randomly.
+
+ "file " - Downloaded filename - MAX_FILENAME_COLS chars + 1
+ "xx% " or "100%" - percentage - 4 chars
+ "[]" - progress bar decorations - 2 chars
+ " nnn.nnK" - downloaded bytes - 7 chars + 1
+ " 12.5KB/s" - download rate - 8 chars + 1
+ " eta 36m 51s" - ETA - 14 chars
+
+ "=====>..." - progress bar - the rest
+ */
+
+ /* TODO: Ask the Turkish Translators to fix their translation for the "done"
+ * mode of progress bar. Use one less character. Once that is done, redice
+ * PROGRESS_ETA_LEN by 1.
+ */
+#define PROGRESS_FILENAME_LEN MAX_FILENAME_COLS + 1
+#define PROGRESS_PERCENT_LEN 4
+#define PROGRESS_DECORAT_LEN 2
+#define PROGRESS_FILESIZE_LEN 7 + 1
+#define PROGRESS_DWNLOAD_RATE 8 + 2
+#define PROGRESS_ETA_LEN 15
+
+ int progress_size = bp->width - (PROGRESS_FILENAME_LEN + PROGRESS_PERCENT_LEN +
+ PROGRESS_DECORAT_LEN + PROGRESS_FILESIZE_LEN +
+ PROGRESS_DWNLOAD_RATE + PROGRESS_ETA_LEN);
+
+ /* The difference between the number of bytes used,
+ and the number of columns used. */
+ int bytes_cols_diff = 0;
+ int cols_diff;
+ const char *down_size;
+
+ memset (bp->buffer, '\0', BUF_LEN);
+
+ if (progress_size < 5)
+ progress_size = 0;
+
+ if (orig_filename_cols <= MAX_FILENAME_COLS)
+ {
+ padding = MAX_FILENAME_COLS - orig_filename_cols;
+ p += sprintf (p, "%s ", bp->f_download);
+ memset (p, ' ', padding);
+ p += padding;
+ }
+ else
+ {
+ int offset_cols;
+ int bytes_in_filename, offset_bytes, col;
+ int *cols_ret = &col;
+
+#define MIN_SCROLL_TEXT 5
+ if ((orig_filename_cols > MAX_FILENAME_COLS + MIN_SCROLL_TEXT) &&
+ !opt.noscroll &&
+ !done)
+ {
+ offset_cols = ((int) bp->tick + orig_filename_cols + MAX_FILENAME_COLS / 2)
+ % (orig_filename_cols + MAX_FILENAME_COLS);
+ if (offset_cols > orig_filename_cols)
+ {
+ padding = MAX_FILENAME_COLS - (offset_cols - orig_filename_cols);
+ memset(p, ' ', padding);
+ p += padding;
+ offset_cols = 0;
+ }
+ else
+ padding = 0;
+ }
+ else
+ {
+ padding = 0;
+ offset_cols = 0;
+ }
+ offset_bytes = cols_to_bytes (bp->f_download, offset_cols, cols_ret);
+ bytes_in_filename = cols_to_bytes (bp->f_download + offset_bytes,
+ MAX_FILENAME_COLS - padding,
+ cols_ret);
+ memcpy (p, bp->f_download + offset_bytes, bytes_in_filename);
+ p += bytes_in_filename;
+ padding = MAX_FILENAME_COLS - (padding + *cols_ret);
+ memset (p, ' ', padding + 1);
+ p += padding + 1;
+ }
+
+ /* "xx% " */
+ if (bp->total_length > 0)
+ {
+ int percentage = 100.0 * size / bp->total_length;
+ assert (percentage <= 100);
+ p += sprintf (p, "%3d%%", percentage);
+ }
+ else
+ {
+ memset (p, ' ', PROGRESS_PERCENT_LEN);
+ p += PROGRESS_PERCENT_LEN;
+ }
+
+ /* The progress bar: "[====> ]" or "[++==> ]". */
+ if (progress_size && bp->total_length > 0)
+ {
+ /* 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;
+
+ assert (dlsz <= progress_size);
+ assert (insz <= dlsz);
+
+ *p++ = '[';
+ begin = p;
+
+ /* Print the initial portion of the download with '+' chars, the
+ rest with '=' and one '>'. */
+ memset (p, '+', insz);
+ p += insz;
+
+ dlsz -= insz;
+ if (dlsz > 0)
+ {
+ memset (p, '=', dlsz-1);
+ p += dlsz - 1;
+ *p++ = '>';
+ }
+
+ memset (p, ' ', (progress_size - (p - begin)));
+ p += (progress_size - (p - begin));
+ *p++ = ']';
+ }
+ else if (progress_size)
+ {
+ /* If we can't draw a real progress bar, then at least show
+ *something* to the user. */
+ int ind = bp->tick % (progress_size * 2 - 6);
+ int i, pos;
+
+ /* Make the star move in two directions. */
+ if (ind < progress_size - 2)
+ pos = ind + 1;
+ else
+ pos = progress_size - (ind - progress_size + 5);
+
+ *p++ = '[';
+ for (i = 0; i < progress_size; i++)
+ {
+ if (i == pos - 1) *p++ = '<';
+ else if (i == pos ) *p++ = '=';
+ else if (i == pos + 1) *p++ = '>';
+ else
+ *p++ = ' ';
+ }
+ *p++ = ']';
+
+ }
+ ++bp->tick;
+
+ /* " 234.56M" */
+ down_size = human_readable (size, 1000, 2);
+ cols_diff = PROGRESS_FILESIZE_LEN - count_cols (down_size);
+ memset (p, ' ', cols_diff);
+ p += cols_diff;
+ p += sprintf (p, "%s", down_size);
+
+ /* " 12.52Kb/s or 12.52KB/s" */
+ if (hist->total_time > 0 && hist->total_bytes)
+ {
+ static const char *short_units[] = { " B/s", "KB/s", "MB/s", "GB/s" };
+ static const char *short_units_bits[] = { " b/s", "Kb/s", "Mb/s", "Gb/s" };
+ int units = 0;
+ /* 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);
+ p += sprintf (p, " %4.*f%s", dlspeed >= 99.95 ? 0 : dlspeed >= 9.995 ? 1 : 2,
+ dlspeed, !opt.report_bps ? short_units[units] : short_units_bits[units]);
+ }
+ else
+ APPEND_LITERAL (" --.-KB/s");
+
+ if (!done)
+ {
+ /* " 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 > 3)
+ {
+ 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 < ETA_REFRESH_INTERVAL)
+ 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. */
+ wgint bytes_remaining = bp->total_length - size;
+ double eta_ = dl_total_time * bytes_remaining / bp->count;
+ if (eta_ >= INT_MAX - 1)
+ goto skip_eta;
+ eta = (int) (eta_ + 0.5);
+ bp->last_eta_value = eta;
+ bp->last_eta_time = dl_total_time;
+ }
+
+ p += sprintf (p, get_eta(&bytes_cols_diff),
+ eta_to_human_short (eta, false));
+ }
+ else if (bp->total_length > 0)
+ {
+ skip_eta:
+ memset (p, ' ', PROGRESS_ETA_LEN);
+ p += PROGRESS_ETA_LEN;
+ }
+ }
+ else
+ {
+ /* When the download is done, print the elapsed time. */
+ int nbytes;
+ int ncols;
+
+ /* Note to translators: this should not take up more room than
+ available here (6 columns). Abbreviate if necessary. */
+ strcpy (p, _(" in "));
+ nbytes = strlen (p);
+ ncols = count_cols (p);
+ bytes_cols_diff = nbytes - ncols;
+ if (dl_total_time >= 10)
+ ncols += sprintf (p + nbytes, "%s", eta_to_human_short ((int) (dl_total_time + 0.5), false));
+ else
+ ncols += sprintf (p + nbytes, "%ss", print_decimal (dl_total_time));
+ p += ncols + bytes_cols_diff;
+ memset (p, ' ', PROGRESS_ETA_LEN - ncols);
+ p += PROGRESS_ETA_LEN - ncols;
+ }
+
+ padding = bp->width - count_cols (bp->buffer);
+ assert (padding >= 0 && "Padding length became non-positive!");
+ padding = padding > 0 ? padding : 0;
+ memset (p, ' ', padding);
+ p += padding;
+ *p = '\0';
+
+ /* 2014-11-14 Darshit Shah <darnir@gmail.com>
+ * Assert that the length of the progress bar is lesser than the size of the
+ * screen with which we are dealing. This assertion *MUST* always be removed
+ * from the release code since we do not want Wget to crash and burn when the
+ * assertion fails. Instead Wget should continue downloading and display a
+ * horrible and irritating progress bar that spams the screen with newlines.
+ *
+ * By default, all assertions are disabled in a Wget build and are enabled
+ * only with the --enable-assert configure option.
+ */
+ assert (count_cols (bp->buffer) == bp->width);
+}
+
+/* Print the contents of the buffer as a one-line ASCII "image" so
+ that it can be overwritten next time. */
+
+static void
+display_image (char *buf)
+{
+ bool old = log_set_save_context (false);
+ logputs (LOG_PROGRESS, "\r");
+ logputs (LOG_PROGRESS, buf);
+ log_set_save_context (old);
+}
+
+static void
+bar_set_params (char *params)
+{
+ if (params)
+ {
+ char *param = strtok (params, ":");
+ do
+ {
+ if (0 == strcmp (param, "force"))
+ current_impl_locked = 1;
+ else if (0 == strcmp (param, "noscroll"))
+ opt.noscroll = true;
+ } while ((param = strtok (NULL, ":")) != NULL);
+ }
+
+ if (((opt.lfilename && opt.show_progress != 1)
+#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))
+#endif
+ )
+ && !current_impl_locked)
+ {
+ /* We're not printing to a TTY, so revert to the fallback
+ display. #### We're recursively calling
+ set_progress_implementation here, which is slightly kludgy.
+ It would be nicer if we provided that function a return value
+ indicating a failure of some sort. */
+ set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
+ return;
+ }
+}
+
+#ifdef SIGWINCH
+void
+progress_handle_sigwinch (int sig _GL_UNUSED)
+{
+ 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 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_short (int secs, bool condensed)
+{
+ static char buf[10]; /* 8 should be enough, but just in case */
+ static int last = -1;
+ const char *space = condensed ? "" : " ";
+
+ /* 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%s%ds", secs / 60, space, secs % 60);
+ else if (secs < 48 * 3600)
+ sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60);
+ else if (secs < 100 * 86400)
+ sprintf (buf, "%dd%s%dh", secs / 86400, space, (secs / 3600) % 24);
+ else
+ /* even (2^31-1)/86400 doesn't overflow BUF. */
+ sprintf (buf, "%dd", secs / 86400);
+
+ return buf;
+}