]> sjero.net Git - wget/blob - src/progress.c
[svn] Logging system bugfixes and improvements.
[wget] / src / progress.c
1 /* Download progress.
2    Copyright (C) 2001 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #else
27 # include <strings.h>
28 #endif /* HAVE_STRING_H */
29 #include <assert.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33 #ifdef HAVE_SIGNAL_H
34 # include <signal.h>
35 #endif
36
37 #include "wget.h"
38 #include "progress.h"
39 #include "utils.h"
40 #include "retr.h"
41
42 struct progress_implementation {
43   char *name;
44   void *(*create) (long, long);
45   void (*update) (void *, long, long);
46   void (*finish) (void *, long);
47   void (*set_params) (const char *);
48 };
49
50 /* Necessary forward declarations. */
51
52 static void *dot_create PARAMS ((long, long));
53 static void dot_update PARAMS ((void *, long, long));
54 static void dot_finish PARAMS ((void *, long));
55 static void dot_set_params PARAMS ((const char *));
56
57 static void *bar_create PARAMS ((long, long));
58 static void bar_update PARAMS ((void *, long, long));
59 static void bar_finish PARAMS ((void *, long));
60 static void bar_set_params PARAMS ((const char *));
61
62 static struct progress_implementation implementations[] = {
63   { "dot", dot_create, dot_update, dot_finish, dot_set_params },
64   { "bar", bar_create, bar_update, bar_finish, bar_set_params }
65 };
66 static struct progress_implementation *current_impl;
67 static int current_impl_locked;
68
69 /* Progress implementation used by default.  Can be overriden in
70    wgetrc or by the fallback one.  */
71
72 #define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
73
74 /* Fallnback progress implementation should be something that works
75    under all display types.  If you put something other than "dot"
76    here, remember that bar_set_params tries to switch to this if we're
77    not running on a TTY.  So changing this to "bar" could cause
78    infloop.  */
79
80 #define FALLBACK_PROGRESS_IMPLEMENTATION "dot"
81
82 /* Return non-zero if NAME names a valid progress bar implementation.
83    The characters after the first : will be ignored.  */
84
85 int
86 valid_progress_implementation_p (const char *name)
87 {
88   int i = 0;
89   struct progress_implementation *pi = implementations;
90   char *colon = strchr (name, ':');
91   int namelen = colon ? colon - name : strlen (name);
92
93   for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
94     if (!strncmp (pi->name, name, namelen))
95       return 1;
96   return 0;
97 }
98
99 /* Set the progress implementation to NAME.  */
100
101 void
102 set_progress_implementation (const char *name)
103 {
104   int i, namelen;
105   struct progress_implementation *pi = implementations;
106   char *colon;
107
108   if (!name)
109     name = DEFAULT_PROGRESS_IMPLEMENTATION;
110
111   colon = strchr (name, ':');
112   namelen = colon ? colon - name : strlen (name);
113
114   for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
115     if (!strncmp (pi->name, name, namelen))
116       {
117         current_impl = pi;
118         current_impl_locked = 0;
119
120         if (colon)
121           /* We call pi->set_params even if colon is NULL because we
122              want to give the implementation a chance to set up some
123              things it needs to run.  */
124           ++colon;
125
126         if (pi->set_params)
127           pi->set_params (colon);
128         return;
129       }
130   abort ();
131 }
132
133 static int output_redirected;
134
135 void
136 progress_schedule_redirect (void)
137 {
138   output_redirected = 1;
139 }
140
141 /* Create a progress gauge.  INITIAL is the number of bytes the
142    download starts from (zero if the download starts from scratch).
143    TOTAL is the expected total number of bytes in this download.  If
144    TOTAL is zero, it means that the download size is not known in
145    advance.  */
146
147 void *
148 progress_create (long initial, long total)
149 {
150   /* Check if the log status has changed under our feet. */
151   if (output_redirected)
152     {
153       if (!current_impl_locked)
154         set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
155       output_redirected = 0;
156     }
157
158   return current_impl->create (initial, total);
159 }
160
161 /* Inform the progress gauge of newly received bytes.  DLTIME is the
162    time in milliseconds since the beginning of the download.  */
163
164 void
165 progress_update (void *progress, long howmuch, long dltime)
166 {
167   current_impl->update (progress, howmuch, dltime);
168 }
169
170 /* Tell the progress gauge to clean up.  Calling this will free the
171    PROGRESS object, the further use of which is not allowed.  */
172
173 void
174 progress_finish (void *progress, long dltime)
175 {
176   current_impl->finish (progress, dltime);
177 }
178 \f
179 /* Dot-printing. */
180
181 struct dot_progress {
182   long initial_length;          /* how many bytes have been downloaded
183                                    previously. */
184   long total_length;            /* expected total byte count when the
185                                    download finishes */
186
187   int accumulated;
188
189   int rows;                     /* number of rows printed so far */
190   int dots;                     /* number of dots printed in this row */
191   long last_timer_value;
192 };
193
194 /* Dot-progress backend for progress_create. */
195
196 static void *
197 dot_create (long initial, long total)
198 {
199   struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
200
201   memset (dp, 0, sizeof (*dp));
202
203   dp->initial_length = initial;
204   dp->total_length   = total;
205
206   if (dp->initial_length)
207     {
208       int dot_bytes = opt.dot_bytes;
209       long row_bytes = opt.dot_bytes * opt.dots_in_line;
210
211       int remainder = (int) (dp->initial_length % row_bytes);
212       long skipped = dp->initial_length - remainder;
213
214       if (skipped)
215         {
216           int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
217           int skipped_k_len = numdigit (skipped_k);
218           if (skipped_k_len < 5)
219             skipped_k_len = 5;
220
221           /* Align the [ skipping ... ] line with the dots.  To do
222              that, insert the number of spaces equal to the number of
223              digits in the skipped amount in K.  */
224           logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"),
225                      2 + skipped_k_len, "", skipped_k);
226         }
227
228       logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
229       for (; remainder >= dot_bytes; remainder -= dot_bytes)
230         {
231           if (dp->dots % opt.dot_spacing == 0)
232             logputs (LOG_VERBOSE, " ");
233           logputs (LOG_VERBOSE, ",");
234           ++dp->dots;
235         }
236       assert (dp->dots < opt.dots_in_line);
237
238       dp->accumulated = remainder;
239       dp->rows = skipped / row_bytes;
240     }
241
242   return dp;
243 }
244
245 static void
246 print_percentage (long bytes, long expected)
247 {
248   int percentage = (int)(100.0 * bytes / expected);
249   logprintf (LOG_VERBOSE, "%3d%%", percentage);
250 }
251
252 static void
253 print_download_speed (struct dot_progress *dp, long bytes, long dltime)
254 {
255   logprintf (LOG_VERBOSE, " %s",
256              retr_rate (bytes, dltime - dp->last_timer_value, 1));
257   dp->last_timer_value = dltime;
258 }
259
260 /* Dot-progress backend for progress_update. */
261
262 static void
263 dot_update (void *progress, long howmuch, long dltime)
264 {
265   struct dot_progress *dp = progress;
266   int dot_bytes = opt.dot_bytes;
267   long row_bytes = opt.dot_bytes * opt.dots_in_line;
268
269   log_set_flush (0);
270
271   dp->accumulated += howmuch;
272   for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
273     {
274       if (dp->dots == 0)
275         logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
276
277       if (dp->dots % opt.dot_spacing == 0)
278         logputs (LOG_VERBOSE, " ");
279       logputs (LOG_VERBOSE, ".");
280
281       ++dp->dots;
282       if (dp->dots >= opt.dots_in_line)
283         {
284           long row_qty = row_bytes;
285           if (dp->rows == dp->initial_length / row_bytes)
286             row_qty -= dp->initial_length % row_bytes;
287
288           ++dp->rows;
289           dp->dots = 0;
290
291           if (dp->total_length)
292             print_percentage (dp->rows * row_bytes, dp->total_length);
293           print_download_speed (dp, row_qty, dltime);
294         }
295     }
296
297   log_set_flush (1);
298 }
299
300 /* Dot-progress backend for progress_finish. */
301
302 static void
303 dot_finish (void *progress, long dltime)
304 {
305   struct dot_progress *dp = progress;
306   int dot_bytes = opt.dot_bytes;
307   long row_bytes = opt.dot_bytes * opt.dots_in_line;
308   int i;
309
310   log_set_flush (0);
311
312   if (dp->dots == 0)
313     logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
314   for (i = dp->dots; i < opt.dots_in_line; i++)
315     {
316       if (i % opt.dot_spacing == 0)
317         logputs (LOG_VERBOSE, " ");
318       logputs (LOG_VERBOSE, " ");
319     }
320   if (dp->total_length)
321     {
322       print_percentage (dp->rows * row_bytes
323                         + dp->dots * dot_bytes
324                         + dp->accumulated,
325                         dp->total_length);
326     }
327
328   {
329     long row_qty = dp->dots * dot_bytes + dp->accumulated;
330     if (dp->rows == dp->initial_length / row_bytes)
331       row_qty -= dp->initial_length % row_bytes;
332     print_download_speed (dp, row_qty, dltime);
333   }
334
335   logputs (LOG_VERBOSE, "\n\n");
336   log_set_flush (0);
337
338   xfree (dp);
339 }
340
341 /* This function interprets the progress "parameters".  For example,
342    if Wget is invoked with --progress=dot:mega, it will set the
343    "dot-style" to "mega".  Valid styles are default, binary, mega, and
344    giga.  */
345
346 static void
347 dot_set_params (const char *params)
348 {
349   if (!params || !*params)
350     params = opt.dot_style;
351
352   if (!params)
353     return;
354
355   /* We use this to set the retrieval style.  */
356   if (!strcasecmp (params, "default"))
357     {
358       /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
359          line.  */
360       opt.dot_bytes = 1024;
361       opt.dot_spacing = 10;
362       opt.dots_in_line = 50;
363     }
364   else if (!strcasecmp (params, "binary"))
365     {
366       /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
367          (384K) in a line.  */
368       opt.dot_bytes = 8192;
369       opt.dot_spacing = 16;
370       opt.dots_in_line = 48;
371     }
372   else if (!strcasecmp (params, "mega"))
373     {
374       /* "Mega" retrieval, for retrieving very long files; each dot is
375          64K, 8 dots in a cluster, 6 clusters (3M) in a line.  */
376       opt.dot_bytes = 65536L;
377       opt.dot_spacing = 8;
378       opt.dots_in_line = 48;
379     }
380   else if (!strcasecmp (params, "giga"))
381     {
382       /* "Giga" retrieval, for retrieving very very *very* long files;
383          each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
384          line.  */
385       opt.dot_bytes = (1L << 20);
386       opt.dot_spacing = 8;
387       opt.dots_in_line = 32;
388     }
389   else
390     fprintf (stderr,
391              _("Invalid dot style specification `%s'; leaving unchanged.\n"),
392              params);
393 }
394 \f
395 /* "Thermometer" (bar) progress. */
396
397 /* Assumed screen width if we can't find the real value.  */
398 #define DEFAULT_SCREEN_WIDTH 80
399
400 /* Minimum screen width we'll try to work with.  If this is too small,
401    create_image will overflow the buffer.  */
402 #define MINIMUM_SCREEN_WIDTH 45
403
404 static int screen_width = DEFAULT_SCREEN_WIDTH;
405
406 struct bar_progress {
407   long initial_length;          /* how many bytes have been downloaded
408                                    previously. */
409   long total_length;            /* expected total byte count when the
410                                    download finishes */
411   long count;                   /* bytes downloaded so far */
412
413   long last_update;             /* time of the last screen update. */
414
415   int width;                    /* screen width we're using at the
416                                    time the progress gauge was
417                                    created.  this is different from
418                                    the screen_width global variable in
419                                    that the latter can be changed by a
420                                    signal. */
421   char *buffer;                 /* buffer where the bar "image" is
422                                    stored. */
423   int tick;
424 };
425
426 static void create_image PARAMS ((struct bar_progress *, long));
427 static void display_image PARAMS ((char *));
428
429 static void *
430 bar_create (long initial, long total)
431 {
432   struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
433
434   memset (bp, 0, sizeof (*bp));
435
436   bp->initial_length = initial;
437   bp->total_length   = total;
438
439   /* - 1 because we don't want to use the last screen column. */
440   bp->width = screen_width - 1;
441   /* + 1 for the terminating zero. */
442   bp->buffer = xmalloc (bp->width + 1);
443
444   logputs (LOG_VERBOSE, "\n");
445
446   create_image (bp, 0);
447   display_image (bp->buffer);
448
449   return bp;
450 }
451
452 static void
453 bar_update (void *progress, long howmuch, long dltime)
454 {
455   struct bar_progress *bp = progress;
456   int force_update = 0;
457
458   bp->count += howmuch;
459   if (bp->total_length > 0
460       && bp->count + bp->initial_length > bp->total_length)
461     /* We could be downloading more than total_length, e.g. when the
462        server sends an incorrect Content-Length header.  In that case,
463        adjust bp->total_length to the new reality, so that the code in
464        create_image() that depends on total size being smaller or
465        equal to the expected size doesn't abort.  */
466     bp->total_length = bp->count + bp->initial_length;
467
468   if (screen_width - 1 != bp->width)
469     {
470       bp->width = screen_width - 1;
471       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
472       force_update = 1;
473     }
474
475   if (dltime - bp->last_update < 200 && !force_update)
476     /* Don't update more often than five times per second. */
477     return;
478
479   bp->last_update = dltime;
480
481   create_image (bp, dltime);
482   display_image (bp->buffer);
483 }
484
485 static void
486 bar_finish (void *progress, long dltime)
487 {
488   struct bar_progress *bp = progress;
489
490   if (dltime == 0)
491     /* If the download was faster than the granularity of the timer,
492        fake some output so that we don't get the ugly "----.--" rate
493        at the download finish.  */
494     dltime = 1;
495
496   create_image (bp, dltime);
497   display_image (bp->buffer);
498
499   logputs (LOG_VERBOSE, "\n\n");
500
501   xfree (bp->buffer);
502   xfree (bp);
503 }
504
505 #define APPEND_LITERAL(s) do {                  \
506   memcpy (p, s, sizeof (s) - 1);                \
507   p += sizeof (s) - 1;                          \
508 } while (0)
509
510 #ifndef MAX
511 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
512 #endif
513
514 static void
515 create_image (struct bar_progress *bp, long dltime)
516 {
517   char *p = bp->buffer;
518   long size = bp->initial_length + bp->count;
519
520   char *size_legible = legible (size);
521   int size_legible_len = strlen (size_legible);
522
523   /* The progress bar should look like this:
524      xx% [=======>             ] nn,nnn 12.34K/s ETA 00:00
525
526      Calculate the geometry.  The idea is to assign as much room as
527      possible to the progress bar.  The other idea is to never let
528      things "jitter", i.e. pad elements that vary in size so that
529      their variance does not affect the placement of other elements.
530      It would be especially bad for the progress bar to be resized
531      randomly.
532
533      "xx% " or "100%"  - percentage               - 4 chars
534      "[]"              - progress bar decorations - 2 chars
535      " nnn,nnn,nnn"    - downloaded bytes         - 12 chars or very rarely more
536      " 1012.56K/s"     - dl rate                  - 11 chars
537      " ETA xx:xx:xx"   - ETA                      - 13 chars
538
539      "=====>..."       - progress bar             - the rest
540   */
541   int dlbytes_size = 1 + MAX (size_legible_len, 11);
542   int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
543
544   if (progress_size < 5)
545     progress_size = 0;
546
547   /* "xx% " */
548   if (bp->total_length > 0)
549     {
550       int percentage = (int)(100.0 * size / bp->total_length);
551
552       assert (percentage <= 100);
553
554       if (percentage < 100)
555         sprintf (p, "%2d%% ", percentage);
556       else
557         strcpy (p, "100%");
558       p += 4;
559     }
560   else
561     APPEND_LITERAL ("    ");
562
563   /* The progress bar: "[====>      ]" */
564   if (progress_size && bp->total_length > 0)
565     {
566       double fraction = (double)size / bp->total_length;
567       int dlsz = (int)(fraction * progress_size);
568       char *begin;
569
570       assert (dlsz <= progress_size);
571
572       *p++ = '[';
573       begin = p;
574
575       if (dlsz > 0)
576         {
577           /* Draw dlsz-1 '=' chars and one arrow char.  */
578           while (dlsz-- > 1)
579             *p++ = '=';
580           *p++ = '>';
581         }
582
583       while (p - begin < progress_size)
584         *p++ = ' ';
585
586       *p++ = ']';
587     }
588   else if (progress_size)
589     {
590       /* If we can't draw a real progress bar, then at least show
591          *something* to the user.  */
592       int ind = bp->tick % (progress_size * 2 - 6);
593       int i, pos;
594
595       /* Make the star move in two directions. */
596       if (ind < progress_size - 2)
597         pos = ind + 1;
598       else
599         pos = progress_size - (ind - progress_size + 5);
600
601       *p++ = '[';
602       for (i = 0; i < progress_size; i++)
603         {
604           if      (i == pos - 1) *p++ = '<';
605           else if (i == pos    ) *p++ = '=';
606           else if (i == pos + 1) *p++ = '>';
607           else
608             *p++ = ' ';
609         }
610       *p++ = ']';
611
612       ++bp->tick;
613     }
614
615   /* " 234,567,890" */
616   sprintf (p, " %-11s", legible (size));
617   p += strlen (p);
618
619   /* " 1012.45K/s" */
620   if (dltime && bp->count)
621     {
622       static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
623       int units = 0;
624       double dlrate = calc_rate (bp->count, dltime, &units);
625       sprintf (p, " %7.2f%s", dlrate, short_units[units]);
626       p += strlen (p);
627     }
628   else
629     APPEND_LITERAL ("   --.--K/s");
630
631   /* " ETA xx:xx:xx" */
632   if (bp->total_length > 0 && bp->count > 0)
633     {
634       int eta, eta_hrs, eta_min, eta_sec;
635       double tm_sofar = (double)dltime / 1000;
636       long bytes_remaining = bp->total_length - size;
637
638       eta = (int) (tm_sofar * bytes_remaining / bp->count);
639
640       eta_hrs = eta / 3600, eta %= 3600;
641       eta_min = eta / 60,   eta %= 60;
642       eta_sec = eta;
643
644       /* Pad until the end of screen.  The padding is dependent on the
645          hour value.  */
646       if (eta_hrs == 0 || eta_hrs > 99)
647         /* Hours not printed: pad with three spaces (two digits and
648            colon). */
649         APPEND_LITERAL ("   ");
650       else if (eta_hrs < 10)
651         /* Hours printed with one digit: pad with one space. */
652         *p++ = ' ';
653       else
654         /* Hours printed with two digits: we're using maximum width,
655            don't pad. */
656         ;
657
658       APPEND_LITERAL (" ETA ");
659
660       if (eta_hrs > 99)
661         /* Bogus value, probably due to a calculation overflow.  Print
662            something safe to avoid overstepping the buffer bounds. */
663         sprintf (p, "--:--");
664       else if (eta_hrs > 0)
665         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
666       else
667         sprintf (p, "%02d:%02d", eta_min, eta_sec);
668       p += strlen (p);
669     }
670   else if (bp->total_length > 0)
671     APPEND_LITERAL ("    ETA --:--");
672
673   assert (p - bp->buffer <= bp->width);
674
675   while (p < bp->buffer + bp->width)
676     *p++ = ' ';
677   *p = '\0';
678 }
679
680 /* Print the contents of the buffer as a one-line ASCII "image" so
681    that it can be overwritten next time.  */
682
683 static void
684 display_image (char *buf)
685 {
686   int old = log_set_save_context (0);
687   logputs (LOG_VERBOSE, "\r");
688   logputs (LOG_VERBOSE, buf);
689   log_set_save_context (old);
690 }
691
692 static void
693 bar_set_params (const char *params)
694 {
695   int sw;
696
697   if (params
698       && 0 == strcmp (params, "force"))
699     current_impl_locked = 1;
700
701   if ((opt.lfilename
702 #ifdef HAVE_ISATTY
703        || !isatty (fileno (stderr))
704 #else
705        1
706 #endif
707        )
708       && !current_impl_locked)
709     {
710       /* We're not printing to a TTY, so revert to the fallback
711          display.  #### We're recursively calling
712          set_progress_implementation here, which is slightly kludgy.
713          It would be nicer if we provided that function a return value
714          indicating a failure of some sort.  */
715       set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
716       return;
717     }
718
719   sw = determine_screen_width ();
720   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
721     screen_width = sw;
722 }
723
724 #ifdef SIGWINCH
725 RETSIGTYPE
726 progress_handle_sigwinch (int sig)
727 {
728   int sw = determine_screen_width ();
729   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
730     screen_width = sw;
731   signal (SIGWINCH, progress_handle_sigwinch);
732 }
733 #endif