]> sjero.net Git - wget/blob - src/progress.c
[svn] New option `--limit-rate'.
[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[ skipping %dK ]"),
199                      2 + skipped_k_len, "", skipped_k);
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           long row_qty = row_bytes;
259           if (dp->rows == dp->initial_length / row_bytes)
260             row_qty -= dp->initial_length % row_bytes;
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           print_download_speed (dp, row_qty, dltime);
268         }
269     }
270
271   log_set_flush (1);
272 }
273
274 /* Dot-progress backend for progress_finish. */
275
276 static void
277 dot_finish (void *progress, long dltime)
278 {
279   struct dot_progress *dp = progress;
280   int dot_bytes = opt.dot_bytes;
281   long row_bytes = opt.dot_bytes * opt.dots_in_line;
282   int i;
283
284   log_set_flush (0);
285
286   for (i = dp->dots; i < opt.dots_in_line; i++)
287     {
288       if (i % opt.dot_spacing == 0)
289         logputs (LOG_VERBOSE, " ");
290       logputs (LOG_VERBOSE, " ");
291     }
292   if (dp->total_length)
293     {
294       print_percentage (dp->rows * row_bytes
295                         + dp->dots * dot_bytes
296                         + dp->accumulated,
297                         dp->total_length);
298     }
299
300   {
301     long row_qty = dp->dots * dot_bytes + dp->accumulated;
302     if (dp->rows == dp->initial_length / row_bytes)
303       row_qty -= dp->initial_length % row_bytes;
304     print_download_speed (dp, row_qty, dltime);
305   }
306
307   logputs (LOG_VERBOSE, "\n\n");
308   log_set_flush (0);
309
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   long last_update;             /* time of the last screen update. */
383
384   int width;                    /* screen width at the time the
385                                    progress gauge was created. */
386   char *buffer;                 /* buffer where the bar "image" is
387                                    stored. */
388
389   int tick;
390 };
391
392 static void create_image PARAMS ((struct bar_progress *, long));
393 static void display_image PARAMS ((char *));
394
395 static void *
396 bar_create (long initial, long total)
397 {
398   struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
399
400   memset (bp, 0, sizeof (*bp));
401
402   bp->initial_length = initial;
403   bp->total_length   = total;
404   bp->width = screen_width;
405   bp->buffer = xmalloc (bp->width + 1);
406
407   logputs (LOG_VERBOSE, "\n");
408
409   create_image (bp, 0);
410   display_image (bp->buffer);
411
412   return bp;
413 }
414
415 static void
416 bar_update (void *progress, long howmuch, long dltime)
417 {
418   struct bar_progress *bp = progress;
419   int force_update = 0;
420
421   bp->count += howmuch;
422   if (bp->total_length > 0
423       && bp->count + bp->initial_length > bp->total_length)
424     /* We could be downloading more than total_length, e.g. when the
425        server sends an incorrect Content-Length header.  In that case,
426        adjust bp->total_length to the new reality, so that the code in
427        create_image() that depends on total size being smaller or
428        equal to the expected size doesn't abort.  */
429     bp->total_length = bp->count + bp->initial_length;
430
431   if (screen_width != bp->width)
432     {
433       bp->width = screen_width;
434       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
435     }
436
437   if (dltime - bp->last_update < 200 && !force_update)
438     /* Don't update more often than every half a second. */
439     return;
440
441   bp->last_update = dltime;
442
443   create_image (bp, dltime);
444   display_image (bp->buffer);
445 }
446
447 static void
448 bar_finish (void *progress, long dltime)
449 {
450   struct bar_progress *bp = progress;
451
452   if (dltime == 0)
453     /* If the download was faster than the granularity of the timer,
454        fake some output so that we don't get the ugly "----.--" rate
455        at the download finish.  */
456     dltime = 1;
457
458   create_image (bp, dltime);
459   display_image (bp->buffer);
460
461   logputs (LOG_VERBOSE, "\n\n");
462
463   xfree (bp->buffer);
464   xfree (bp);
465 }
466
467 static void
468 create_image (struct bar_progress *bp, long dltime)
469 {
470   char *p = bp->buffer;
471   long size = bp->initial_length + bp->count;
472
473   /* The progress bar should look like this:
474      xx% [=======>             ] nn.nnn rrK/s ETA 00:00
475
476      Calculate its geometry:
477
478      "xx% " or "100%"  - percentage                - 4 chars exactly
479      "[]"              - progress bar decorations  - 2 chars exactly
480      " n,nnn,nnn,nnn"  - downloaded bytes          - 14 or less chars
481      " 1012.56K/s"     - dl rate                   - 11 chars exactly
482      " ETA xx:xx:xx"   - ETA                       - 13 or less chars
483
484      "=====>..."       - progress bar content      - the rest
485   */
486   int progress_size = screen_width - (4 + 2 + 14 + 11 + 13);
487
488   if (progress_size < 5)
489     progress_size = 0;
490
491   /* "xx% " */
492   if (bp->total_length > 0)
493     {
494       int percentage = (int)(100.0 * size / bp->total_length);
495
496       assert (percentage <= 100);
497
498       if (percentage < 100)
499         sprintf (p, "%2d%% ", percentage);
500       else
501         strcpy (p, "100%");
502       p += 4;
503     }
504   else
505     {
506       *p++ = ' ';
507       *p++ = ' ';
508       *p++ = ' ';
509       *p++ = ' ';
510     }
511
512   /* The progress bar: "[====>      ]" */
513   if (progress_size && bp->total_length > 0)
514     {
515       double fraction = (double)size / bp->total_length;
516       int dlsz = (int)(fraction * progress_size);
517       char *begin;
518
519       assert (dlsz <= progress_size);
520
521       *p++ = '[';
522       begin = p;
523
524       if (dlsz > 0)
525         {
526           /* Draw dlsz-1 '=' chars and one arrow char.  */
527           while (dlsz-- > 1)
528             *p++ = '=';
529           *p++ = '>';
530         }
531
532       while (p - begin < progress_size)
533         *p++ = ' ';
534
535       *p++ = ']';
536     }
537   else if (progress_size)
538     {
539       /* If we can't draw a real progress bar, then at least show
540          *something* to the user.  */
541       int ind = bp->tick % (progress_size * 2 - 6);
542       int i, pos;
543
544       /* Make the star move in two directions. */
545       if (ind < progress_size - 2)
546         pos = ind + 1;
547       else
548         pos = progress_size - (ind - progress_size + 5);
549
550       *p++ = '[';
551       for (i = 0; i < progress_size; i++)
552         {
553           if      (i == pos - 1) *p++ = '<';
554           else if (i == pos    ) *p++ = '=';
555           else if (i == pos + 1) *p++ = '>';
556           else
557             *p++ = ' ';
558         }
559       *p++ = ']';
560
561       ++bp->tick;
562     }
563
564   /* " 1,234,567" */
565   /* If there are 7 or less digits (9 because of "legible" comas),
566      print the number in constant space.  This will prevent the rest
567      of the line jerking at the beginning of download, but without
568      assigning maximum width in all cases.  */
569   sprintf (p, " %9s", legible (size));
570   p += strlen (p);
571
572   /* " 1012.45K/s" */
573   if (dltime && bp->count)
574     {
575       static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
576       int units = 0;
577       double dlrate = calc_rate (bp->count, dltime, &units);
578       sprintf (p, " %7.2f%s", dlrate, short_units[units]);
579       p += strlen (p);
580     }
581   else
582     {
583       strcpy (p, "   --.--K/s");
584       p += 11;
585     }
586
587   /* " ETA xx:xx:xx" */
588   if (bp->total_length > 0 && bp->count > 0)
589     {
590       int eta, eta_hrs, eta_min, eta_sec;
591       double tm_sofar = (double)dltime / 1000;
592       long bytes_remaining = bp->total_length - size;
593
594       eta = (int) (tm_sofar * bytes_remaining / bp->count);
595
596       eta_hrs = eta / 3600, eta %= 3600;
597       eta_min = eta / 60,   eta %= 60;
598       eta_sec = eta;
599
600       /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
601       /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
602
603       *p++ = ' ';
604       *p++ = 'E';
605       *p++ = 'T';
606       *p++ = 'A';
607       *p++ = ' ';
608
609       if (eta_hrs > 99)
610         /* Bogus value, for whatever reason.  We must avoid overflow. */
611         sprintf (p, "--:--");
612       else if (eta_hrs > 0)
613         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
614       else
615         sprintf (p, "%02d:%02d", eta_min, eta_sec);
616       p += strlen (p);
617     }
618   else if (bp->total_length > 0)
619     {
620       strcpy (p, " ETA --:--");
621       p += 10;
622     }
623
624   assert (p - bp->buffer <= screen_width);
625
626   while (p < bp->buffer + screen_width)
627     *p++ = ' ';
628   *p = '\0';
629 }
630
631 /* Print the contents of the buffer as a one-line ASCII "image" so
632    that it can be overwritten next time.  */
633
634 static void
635 display_image (char *buf)
636 {
637   char *del_buf = alloca (screen_width + 1);
638   memset (del_buf, '\b', screen_width);
639   del_buf[screen_width] = '\0';
640   logputs (LOG_VERBOSE, del_buf);
641   logputs (LOG_VERBOSE, buf);
642 }
643
644 static void
645 bar_set_params (const char *params)
646 {
647   int sw;
648
649   if ((opt.lfilename
650 #ifdef HAVE_ISATTY
651        || !isatty (fileno (stderr))
652 #else
653        1
654 #endif
655        )
656       && !(params != NULL
657            && 0 == strcmp (params, "force")))
658     {
659       /* We're not printing to a TTY, so revert to the fallback
660          display.  #### We're recursively calling
661          set_progress_implementation here, which is slightly kludgy.
662          It would be nicer if that function could resolve this problem
663          itself.  */
664       set_progress_implementation (NULL);
665       return;
666     }
667
668   sw = determine_screen_width ();
669   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
670     screen_width = sw;
671 }
672
673 RETSIGTYPE
674 progress_handle_sigwinch (int sig)
675 {
676   int sw = determine_screen_width ();
677   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
678     screen_width = sw;
679 }