]> sjero.net Git - wget/blob - src/progress.c
[svn] Resurrect opt.dot_style.
[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   if (dp->dots == 0)
292     logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
293   for (i = dp->dots; i < opt.dots_in_line; i++)
294     {
295       if (i % opt.dot_spacing == 0)
296         logputs (LOG_VERBOSE, " ");
297       logputs (LOG_VERBOSE, " ");
298     }
299   if (dp->total_length)
300     {
301       print_percentage (dp->rows * row_bytes
302                         + dp->dots * dot_bytes
303                         + dp->accumulated,
304                         dp->total_length);
305     }
306
307   {
308     long row_qty = dp->dots * dot_bytes + dp->accumulated;
309     if (dp->rows == dp->initial_length / row_bytes)
310       row_qty -= dp->initial_length % row_bytes;
311     print_download_speed (dp, row_qty, dltime);
312   }
313
314   logputs (LOG_VERBOSE, "\n\n");
315   log_set_flush (0);
316
317   xfree (dp);
318 }
319
320 /* This function interprets the progress "parameters".  For example,
321    if Wget is invoked with --progress=dot:mega, it will set the
322    "dot-style" to "mega".  Valid styles are default, binary, mega, and
323    giga.  */
324
325 static void
326 dot_set_params (const char *params)
327 {
328   if (!params || !*params)
329     params = opt.dot_style;
330
331   if (!params)
332     return;
333
334   /* We use this to set the retrieval style.  */
335   if (!strcasecmp (params, "default"))
336     {
337       /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
338          line.  */
339       opt.dot_bytes = 1024;
340       opt.dot_spacing = 10;
341       opt.dots_in_line = 50;
342     }
343   else if (!strcasecmp (params, "binary"))
344     {
345       /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
346          (384K) in a line.  */
347       opt.dot_bytes = 8192;
348       opt.dot_spacing = 16;
349       opt.dots_in_line = 48;
350     }
351   else if (!strcasecmp (params, "mega"))
352     {
353       /* "Mega" retrieval, for retrieving very long files; each dot is
354          64K, 8 dots in a cluster, 6 clusters (3M) in a line.  */
355       opt.dot_bytes = 65536L;
356       opt.dot_spacing = 8;
357       opt.dots_in_line = 48;
358     }
359   else if (!strcasecmp (params, "giga"))
360     {
361       /* "Giga" retrieval, for retrieving very very *very* long files;
362          each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
363          line.  */
364       opt.dot_bytes = (1L << 20);
365       opt.dot_spacing = 8;
366       opt.dots_in_line = 32;
367     }
368   else
369     fprintf (stderr,
370              _("Invalid dot style specification `%s'; leaving unchanged.\n"),
371              params);
372 }
373 \f
374 /* "Thermometer" (bar) progress. */
375
376 /* Assumed screen width if we can't find the real value.  */
377 #define DEFAULT_SCREEN_WIDTH 80
378
379 /* Minimum screen width we'll try to work with.  If this is too small,
380    create_image will overflow the buffer.  */
381 #define MINIMUM_SCREEN_WIDTH 45
382
383 static int screen_width = DEFAULT_SCREEN_WIDTH;
384
385 struct bar_progress {
386   long initial_length;          /* how many bytes have been downloaded
387                                    previously. */
388   long total_length;            /* expected total byte count when the
389                                    download finishes */
390   long count;                   /* bytes downloaded so far */
391
392   long last_update;             /* time of the last screen update. */
393
394   int width;                    /* screen width at the time the
395                                    progress gauge was created. */
396   char *buffer;                 /* buffer where the bar "image" is
397                                    stored. */
398
399   int tick;
400 };
401
402 static void create_image PARAMS ((struct bar_progress *, long));
403 static void display_image PARAMS ((char *));
404
405 static void *
406 bar_create (long initial, long total)
407 {
408   struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
409
410   memset (bp, 0, sizeof (*bp));
411
412   bp->initial_length = initial;
413   bp->total_length   = total;
414   bp->width = screen_width;
415   bp->buffer = xmalloc (bp->width + 1);
416
417   logputs (LOG_VERBOSE, "\n");
418
419   create_image (bp, 0);
420   display_image (bp->buffer);
421
422   return bp;
423 }
424
425 static void
426 bar_update (void *progress, long howmuch, long dltime)
427 {
428   struct bar_progress *bp = progress;
429   int force_update = 0;
430
431   bp->count += howmuch;
432   if (bp->total_length > 0
433       && bp->count + bp->initial_length > bp->total_length)
434     /* We could be downloading more than total_length, e.g. when the
435        server sends an incorrect Content-Length header.  In that case,
436        adjust bp->total_length to the new reality, so that the code in
437        create_image() that depends on total size being smaller or
438        equal to the expected size doesn't abort.  */
439     bp->total_length = bp->count + bp->initial_length;
440
441   if (screen_width != bp->width)
442     {
443       bp->width = screen_width;
444       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
445     }
446
447   if (dltime - bp->last_update < 200 && !force_update)
448     /* Don't update more often than every half a second. */
449     return;
450
451   bp->last_update = dltime;
452
453   create_image (bp, dltime);
454   display_image (bp->buffer);
455 }
456
457 static void
458 bar_finish (void *progress, long dltime)
459 {
460   struct bar_progress *bp = progress;
461
462   if (dltime == 0)
463     /* If the download was faster than the granularity of the timer,
464        fake some output so that we don't get the ugly "----.--" rate
465        at the download finish.  */
466     dltime = 1;
467
468   create_image (bp, dltime);
469   display_image (bp->buffer);
470
471   logputs (LOG_VERBOSE, "\n\n");
472
473   xfree (bp->buffer);
474   xfree (bp);
475 }
476
477 static void
478 create_image (struct bar_progress *bp, long dltime)
479 {
480   char *p = bp->buffer;
481   long size = bp->initial_length + bp->count;
482
483   /* The progress bar should look like this:
484      xx% [=======>             ] nn.nnn rrK/s ETA 00:00
485
486      Calculate its geometry:
487
488      "xx% " or "100%"  - percentage                - 4 chars exactly
489      "[]"              - progress bar decorations  - 2 chars exactly
490      " n,nnn,nnn,nnn"  - downloaded bytes          - 14 or less chars
491      " 1012.56K/s"     - dl rate                   - 11 chars exactly
492      " ETA xx:xx:xx"   - ETA                       - 13 or less chars
493
494      "=====>..."       - progress bar content      - the rest
495   */
496   int progress_size = screen_width - (4 + 2 + 14 + 11 + 13);
497
498   if (progress_size < 5)
499     progress_size = 0;
500
501   /* "xx% " */
502   if (bp->total_length > 0)
503     {
504       int percentage = (int)(100.0 * size / bp->total_length);
505
506       assert (percentage <= 100);
507
508       if (percentage < 100)
509         sprintf (p, "%2d%% ", percentage);
510       else
511         strcpy (p, "100%");
512       p += 4;
513     }
514   else
515     {
516       *p++ = ' ';
517       *p++ = ' ';
518       *p++ = ' ';
519       *p++ = ' ';
520     }
521
522   /* The progress bar: "[====>      ]" */
523   if (progress_size && bp->total_length > 0)
524     {
525       double fraction = (double)size / bp->total_length;
526       int dlsz = (int)(fraction * progress_size);
527       char *begin;
528
529       assert (dlsz <= progress_size);
530
531       *p++ = '[';
532       begin = p;
533
534       if (dlsz > 0)
535         {
536           /* Draw dlsz-1 '=' chars and one arrow char.  */
537           while (dlsz-- > 1)
538             *p++ = '=';
539           *p++ = '>';
540         }
541
542       while (p - begin < progress_size)
543         *p++ = ' ';
544
545       *p++ = ']';
546     }
547   else if (progress_size)
548     {
549       /* If we can't draw a real progress bar, then at least show
550          *something* to the user.  */
551       int ind = bp->tick % (progress_size * 2 - 6);
552       int i, pos;
553
554       /* Make the star move in two directions. */
555       if (ind < progress_size - 2)
556         pos = ind + 1;
557       else
558         pos = progress_size - (ind - progress_size + 5);
559
560       *p++ = '[';
561       for (i = 0; i < progress_size; i++)
562         {
563           if      (i == pos - 1) *p++ = '<';
564           else if (i == pos    ) *p++ = '=';
565           else if (i == pos + 1) *p++ = '>';
566           else
567             *p++ = ' ';
568         }
569       *p++ = ']';
570
571       ++bp->tick;
572     }
573
574   /* " 1,234,567" */
575   /* If there are 7 or less digits (9 because of "legible" comas),
576      print the number in constant space.  This will prevent the rest
577      of the line jerking at the beginning of download, but without
578      assigning maximum width in all cases.  */
579   sprintf (p, " %9s", legible (size));
580   p += strlen (p);
581
582   /* " 1012.45K/s" */
583   if (dltime && bp->count)
584     {
585       static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
586       int units = 0;
587       double dlrate = calc_rate (bp->count, dltime, &units);
588       sprintf (p, " %7.2f%s", dlrate, short_units[units]);
589       p += strlen (p);
590     }
591   else
592     {
593       strcpy (p, "   --.--K/s");
594       p += 11;
595     }
596
597   /* " ETA xx:xx:xx" */
598   if (bp->total_length > 0 && bp->count > 0)
599     {
600       int eta, eta_hrs, eta_min, eta_sec;
601       double tm_sofar = (double)dltime / 1000;
602       long bytes_remaining = bp->total_length - size;
603
604       eta = (int) (tm_sofar * bytes_remaining / bp->count);
605
606       eta_hrs = eta / 3600, eta %= 3600;
607       eta_min = eta / 60,   eta %= 60;
608       eta_sec = eta;
609
610       /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
611       /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
612
613       *p++ = ' ';
614       *p++ = 'E';
615       *p++ = 'T';
616       *p++ = 'A';
617       *p++ = ' ';
618
619       if (eta_hrs > 99)
620         /* Bogus value, for whatever reason.  We must avoid overflow. */
621         sprintf (p, "--:--");
622       else if (eta_hrs > 0)
623         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
624       else
625         sprintf (p, "%02d:%02d", eta_min, eta_sec);
626       p += strlen (p);
627     }
628   else if (bp->total_length > 0)
629     {
630       strcpy (p, " ETA --:--");
631       p += 10;
632     }
633
634   assert (p - bp->buffer <= screen_width);
635
636   while (p < bp->buffer + screen_width)
637     *p++ = ' ';
638   *p = '\0';
639 }
640
641 /* Print the contents of the buffer as a one-line ASCII "image" so
642    that it can be overwritten next time.  */
643
644 static void
645 display_image (char *buf)
646 {
647   char *del_buf = alloca (screen_width + 1);
648   memset (del_buf, '\b', screen_width);
649   del_buf[screen_width] = '\0';
650   logputs (LOG_VERBOSE, del_buf);
651   logputs (LOG_VERBOSE, buf);
652 }
653
654 static void
655 bar_set_params (const char *params)
656 {
657   int sw;
658
659   if ((opt.lfilename
660 #ifdef HAVE_ISATTY
661        || !isatty (fileno (stderr))
662 #else
663        1
664 #endif
665        )
666       && !(params != NULL
667            && 0 == strcmp (params, "force")))
668     {
669       /* We're not printing to a TTY, so revert to the fallback
670          display.  #### We're recursively calling
671          set_progress_implementation here, which is slightly kludgy.
672          It would be nicer if we provided that function a return value
673          indicating a failure of some sort.  */
674       set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
675       return;
676     }
677
678   sw = determine_screen_width ();
679   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
680     screen_width = sw;
681 }
682
683 RETSIGTYPE
684 progress_handle_sigwinch (int sig)
685 {
686   int sw = determine_screen_width ();
687   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
688     screen_width = sw;
689 }