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