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