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