]> sjero.net Git - wget/blobdiff - src/progress.c
[svn] Don't allow initial_length to exceed total_length.
[wget] / src / progress.c
index 18732c77d794d6c7ebf23465cbca717ab31a4c6d..644c1b2b9dc1571bb3d3dbabbbc5c4bad27f6dbc 100644 (file)
@@ -30,6 +30,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
 
 #include "wget.h"
 #include "progress.h"
@@ -38,10 +41,10 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 
 struct progress_implementation {
   char *name;
-  void *(*create) (long, long);
-  void (*update) (void *, long, long);
-  void (*finish) (void *, long);
-  void (*set_params) (const char *);
+  void *(*create) PARAMS ((long, long));
+  void (*update) PARAMS ((void *, long, long));
+  void (*finish) PARAMS ((void *, long));
+  void (*set_params) PARAMS ((const char *));
 };
 
 /* Necessary forward declarations. */
@@ -61,6 +64,7 @@ static struct progress_implementation implementations[] = {
   { "bar", bar_create, bar_update, bar_finish, bar_set_params }
 };
 static struct progress_implementation *current_impl;
+static int current_impl_locked;
 
 /* Progress implementation used by default.  Can be overriden in
    wgetrc or by the fallback one.  */
@@ -111,6 +115,7 @@ set_progress_implementation (const char *name)
     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
@@ -125,6 +130,14 @@ set_progress_implementation (const char *name)
   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
@@ -134,6 +147,14 @@ set_progress_implementation (const char *name)
 void *
 progress_create (long initial, long 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 (initial, total);
 }
 
@@ -382,6 +403,14 @@ dot_set_params (const char *params)
 
 static int screen_width = DEFAULT_SCREEN_WIDTH;
 
+/* Size of the history table for download speeds. */
+#define DLSPEED_HISTORY_SIZE 30
+
+/* 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
+
 struct bar_progress {
   long initial_length;         /* how many bytes have been downloaded
                                   previously. */
@@ -389,14 +418,37 @@ struct bar_progress {
                                   download finishes */
   long count;                  /* bytes downloaded so far */
 
-  long last_update;            /* time of the last screen update. */
+  long last_screen_update;     /* time of the last screen update. */
 
-  int width;                   /* screen width at the time the
-                                  progress gauge was created. */
+  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;
+  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;
+    long times[DLSPEED_HISTORY_SIZE];
+    long bytes[DLSPEED_HISTORY_SIZE];
+    long summed_times;
+    long summed_bytes;
+    long previous_time;
+  } hist;
+
+  /* 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;
 };
 
 static void create_image PARAMS ((struct bar_progress *, long));
@@ -409,9 +461,17 @@ bar_create (long initial, long total)
 
   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;
-  bp->width = screen_width;
+
+  /* - 1 because we don't want to use the last screen column. */
+  bp->width = screen_width - 1;
+  /* + 1 for the terminating zero. */
   bp->buffer = xmalloc (bp->width + 1);
 
   logputs (LOG_VERBOSE, "\n");
@@ -426,7 +486,9 @@ static void
 bar_update (void *progress, long howmuch, long dltime)
 {
   struct bar_progress *bp = progress;
-  int force_update = 0;
+  struct bar_progress_hist *hist = &bp->hist;
+  int force_screen_update = 0;
+  long delta_time = dltime - hist->previous_time;
 
   bp->count += howmuch;
   if (bp->total_length > 0
@@ -436,22 +498,71 @@ 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;
+
+  /* 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;
+
+#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 != bp->width)
+  if (screen_width - 1 != bp->width)
     {
-      bp->width = screen_width;
+      bp->width = screen_width - 1;
       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
+      force_screen_update = 1;
     }
 
-  if (dltime - bp->last_update < 200 && !force_update)
-    /* Don't update more often than every half a second. */
+  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);
   display_image (bp->buffer);
+  bp->last_screen_update = dltime;
 }
 
 static void
@@ -459,11 +570,10 @@ bar_finish (void *progress, long 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);
   display_image (bp->buffer);
@@ -474,26 +584,46 @@ bar_finish (void *progress, long dltime)
   xfree (bp);
 }
 
+#define APPEND_LITERAL(s) do {                 \
+  memcpy (p, s, sizeof (s) - 1);               \
+  p += sizeof (s) - 1;                         \
+} while (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, long dl_total_time)
 {
   char *p = bp->buffer;
   long size = bp->initial_length + bp->count;
 
-  /* The progress bar should look like this:
-     xx% [=======>             ] nn.nnn rrK/s ETA 00:00
-
-     Calculate its geometry:
+  char *size_legible = legible (size);
+  int size_legible_len = strlen (size_legible);
 
-     "xx% " or "100%"  - percentage                - 4 chars exactly
-     "[]"              - progress bar decorations  - 2 chars exactly
-     " n,nnn,nnn,nnn"  - downloaded bytes          - 14 or less chars
-     " 1012.56K/s"     - dl rate                   - 11 chars exactly
-     " ETA xx:xx:xx"   - ETA                       - 13 or less chars
+  struct bar_progress_hist *hist = &bp->hist;
 
-     "=====>..."       - progress bar content      - the rest
+  /* The progress bar should look like this:
+     xx% [=======>             ] nn,nnn 12.34K/s ETA 00:00
+
+     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.
+
+     "xx% " or "100%"  - percentage               - 4 chars
+     "[]"              - progress bar decorations - 2 chars
+     " nnn,nnn,nnn"    - downloaded bytes         - 12 chars or very rarely more
+     " 1012.56K/s"     - dl rate                  - 11 chars
+     " ETA xx:xx:xx"   - ETA                      - 13 chars
+
+     "=====>..."       - progress bar             - the rest
   */
-  int progress_size = screen_width - (4 + 2 + 14 + 11 + 13);
+  int dlbytes_size = 1 + MAX (size_legible_len, 11);
+  int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
 
   if (progress_size < 5)
     progress_size = 0;
@@ -512,12 +642,7 @@ create_image (struct bar_progress *bp, long dltime)
       p += 4;
     }
   else
-    {
-      *p++ = ' ';
-      *p++ = ' ';
-      *p++ = ' ';
-      *p++ = ' ';
-    }
+    APPEND_LITERAL ("    ");
 
   /* The progress bar: "[====>      ]" */
   if (progress_size && bp->total_length > 0)
@@ -571,69 +696,81 @@ create_image (struct bar_progress *bp, long dltime)
       ++bp->tick;
     }
 
-  /* " 1,234,567" */
-  /* If there are 7 or less digits (9 because of "legible" comas),
-     print the number in constant space.  This will prevent the rest
-     of the line jerking at the beginning of download, but without
-     assigning maximum width in all cases.  */
-  sprintf (p, " %9s", legible (size));
+  /* " 234,567,890" */
+  sprintf (p, " %-11s", legible (size));
   p += strlen (p);
 
   /* " 1012.45K/s" */
-  if (dltime && bp->count)
+  if (hist->summed_times && hist->summed_bytes)
     {
       static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
       int units = 0;
-      double dlrate = calc_rate (bp->count, dltime, &units);
+      double dlrate;
+      dlrate = calc_rate (hist->summed_bytes, hist->summed_times, &units);
       sprintf (p, " %7.2f%s", dlrate, short_units[units]);
       p += strlen (p);
     }
   else
-    {
-      strcpy (p, "   --.--K/s");
-      p += 11;
-    }
+    APPEND_LITERAL ("   --.--K/s");
 
   /* " ETA xx:xx:xx" */
-  if (bp->total_length > 0 && bp->count > 0)
+  if (bp->total_length > 0 && dl_total_time > 3000)
     {
-      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);
+      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
+       {
+         /* 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;
 
-      /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
-      /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
-
-      *p++ = ' ';
-      *p++ = 'E';
-      *p++ = 'T';
-      *p++ = 'A';
-      *p++ = ' ';
-
       if (eta_hrs > 99)
-       /* Bogus value, for whatever reason.  We must avoid overflow. */
-       sprintf (p, "--:--");
-      else if (eta_hrs > 0)
-       sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
+       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);
+       }
       else
-       sprintf (p, "%02d:%02d", eta_min, eta_sec);
+       {
+         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);
+       }
       p += strlen (p);
     }
   else if (bp->total_length > 0)
     {
-      strcpy (p, " ETA --:--");
-      p += 10;
+    no_eta:
+      APPEND_LITERAL ("             ");
     }
 
-  assert (p - bp->buffer <= screen_width);
+  assert (p - bp->buffer <= bp->width);
 
-  while (p < bp->buffer + screen_width)
+  while (p < bp->buffer + bp->width)
     *p++ = ' ';
   *p = '\0';
 }
@@ -644,11 +781,10 @@ create_image (struct bar_progress *bp, long dltime)
 static void
 display_image (char *buf)
 {
-  char *del_buf = alloca (screen_width + 1);
-  memset (del_buf, '\b', screen_width);
-  del_buf[screen_width] = '\0';
-  logputs (LOG_VERBOSE, del_buf);
+  int old = log_set_save_context (0);
+  logputs (LOG_VERBOSE, "\r");
   logputs (LOG_VERBOSE, buf);
+  log_set_save_context (old);
 }
 
 static void
@@ -656,6 +792,10 @@ bar_set_params (const char *params)
 {
   int sw;
 
+  if (params
+      && 0 == strcmp (params, "force"))
+    current_impl_locked = 1;
+
   if ((opt.lfilename
 #ifdef HAVE_ISATTY
        || !isatty (fileno (stderr))
@@ -663,8 +803,7 @@ bar_set_params (const char *params)
        1
 #endif
        )
-      && !(params != NULL
-          && 0 == strcmp (params, "force")))
+      && !current_impl_locked)
     {
       /* We're not printing to a TTY, so revert to the fallback
         display.  #### We're recursively calling
@@ -680,10 +819,13 @@ bar_set_params (const char *params)
     screen_width = sw;
 }
 
+#ifdef SIGWINCH
 RETSIGTYPE
 progress_handle_sigwinch (int sig)
 {
   int sw = determine_screen_width ();
   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
     screen_width = sw;
+  signal (SIGWINCH, progress_handle_sigwinch);
 }
+#endif