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