]> sjero.net Git - wget/blob - src/progress.c
[svn] Minor progress bar fixes.
[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
31 #include "wget.h"
32 #include "progress.h"
33 #include "utils.h"
34 #include "retr.h"
35
36 struct progress_implementation {
37   char *name;
38   void *(*create) (long, long);
39   void (*update) (void *, long);
40   void (*finish) (void *);
41   void (*set_params) (const char *);
42 };
43
44 /* Necessary forward declarations. */
45
46 static void *dot_create PARAMS ((long, long));
47 static void dot_update PARAMS ((void *, long));
48 static void dot_finish PARAMS ((void *));
49 static void dot_set_params PARAMS ((const char *));
50
51 static void *bar_create PARAMS ((long, long));
52 static void bar_update PARAMS ((void *, long));
53 static void bar_finish PARAMS ((void *));
54 static void bar_set_params PARAMS ((const char *));
55
56 static struct progress_implementation implementations[] = {
57   { "dot", dot_create, dot_update, dot_finish, dot_set_params },
58   { "bar", bar_create, bar_update, bar_finish, bar_set_params }
59 };
60 static struct progress_implementation *current_impl;
61
62 /* Default progress implementation should be something that works
63    under all display types.  If you put something other than "dot"
64    here, remember that bar_set_params tries to switch to this if we're
65    not running on a TTY.  So changing this to "bar" could cause
66    infloop.  */
67
68 #define DEFAULT_PROGRESS_IMPLEMENTATION "dot"
69
70 /* Return non-zero if NAME names a valid progress bar implementation.
71    The characters after the first : will be ignored.  */
72
73 int
74 valid_progress_implementation_p (const char *name)
75 {
76   int i = 0;
77   struct progress_implementation *pi = implementations;
78   char *colon = strchr (name, ':');
79   int namelen = colon ? colon - name : strlen (name);
80
81   for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
82     if (!strncmp (pi->name, name, namelen))
83       return 1;
84   return 0;
85 }
86
87 /* Set the progress implementation to NAME.  */
88
89 void
90 set_progress_implementation (const char *name)
91 {
92   int i, namelen;
93   struct progress_implementation *pi = implementations;
94   char *colon;
95
96   if (!name)
97     name = DEFAULT_PROGRESS_IMPLEMENTATION;
98
99   colon = strchr (name, ':');
100   namelen = colon ? colon - name : strlen (name);
101
102   for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
103     if (!strncmp (pi->name, name, namelen))
104       {
105         current_impl = pi;
106
107         if (colon)
108           /* We call pi->set_params even if colon is NULL because we
109              want to give the implementation a chance to set up some
110              things it needs to run.  */
111           ++colon;
112
113         if (pi->set_params)
114           pi->set_params (colon);
115         return;
116       }
117   abort ();
118 }
119
120 /* Create a progress gauge.  INITIAL is the number of bytes the
121    download starts from (zero if the download starts from scratch).
122    TOTAL is the expected total number of bytes in this download.  If
123    TOTAL is zero, it means that the download size is not known in
124    advance.  */
125
126 void *
127 progress_create (long initial, long total)
128 {
129   return current_impl->create (initial, total);
130 }
131
132 /* Inform the progress gauge of newly received bytes. */
133
134 void
135 progress_update (void *progress, long howmuch)
136 {
137   current_impl->update (progress, howmuch);
138 }
139
140 /* Tell the progress gauge to clean up.  Calling this will free the
141    PROGRESS object, the further use of which is not allowed.  */
142
143 void
144 progress_finish (void *progress)
145 {
146   current_impl->finish (progress);
147 }
148 \f
149 /* Dot-printing. */
150
151 struct dot_progress {
152   long initial_length;          /* how many bytes have been downloaded
153                                    previously. */
154   long total_length;            /* expected total byte count when the
155                                    download finishes */
156
157   int accumulated;
158
159   int rows;                     /* number of rows printed so far */
160   int dots;                     /* number of dots printed in this row */
161
162   struct wget_timer *timer;     /* timer used to measure per-row
163                                    download rates. */
164   long last_timer_value;
165 };
166
167 /* Dot-progress backend for progress_create. */
168
169 static void *
170 dot_create (long initial, long total)
171 {
172   struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
173
174   memset (dp, 0, sizeof (*dp));
175
176   dp->initial_length = initial;
177   dp->total_length   = total;
178   dp->timer = wtimer_new ();
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           logputs (LOG_VERBOSE, "\n      "); /* leave spacing untranslated */
191           logprintf (LOG_VERBOSE, _("[ skipping %dK ]"),
192                      (int) (skipped / 1024));
193         }
194
195       logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
196       for (; remainder >= dot_bytes; remainder -= dot_bytes)
197         {
198           if (dp->dots % opt.dot_spacing == 0)
199             logputs (LOG_VERBOSE, " ");
200           logputs (LOG_VERBOSE, ",");
201           ++dp->dots;
202         }
203       assert (dp->dots < opt.dots_in_line);
204
205       dp->accumulated = remainder;
206       dp->rows = skipped / row_bytes;
207     }
208
209   return dp;
210 }
211
212 static void
213 print_percentage (long bytes, long expected)
214 {
215   int percentage = (int)(100.0 * bytes / expected);
216   logprintf (LOG_VERBOSE, "%3d%%", percentage);
217 }
218
219 static void
220 print_download_speed (struct dot_progress *dp, long bytes)
221 {
222   long timer_value = wtimer_elapsed (dp->timer);
223   logprintf (LOG_VERBOSE, " %s",
224              rate (bytes, timer_value - dp->last_timer_value, 1));
225   dp->last_timer_value = timer_value;
226 }
227
228 /* Dot-progress backend for progress_update. */
229
230 static void
231 dot_update (void *progress, long howmuch)
232 {
233   struct dot_progress *dp = progress;
234   int dot_bytes = opt.dot_bytes;
235   long row_bytes = opt.dot_bytes * opt.dots_in_line;
236
237   log_set_flush (0);
238
239   dp->accumulated += howmuch;
240   for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
241     {
242       if (dp->dots == 0)
243         logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
244
245       if (dp->dots % opt.dot_spacing == 0)
246         logputs (LOG_VERBOSE, " ");
247       logputs (LOG_VERBOSE, ".");
248
249       ++dp->dots;
250       if (dp->dots >= opt.dots_in_line)
251         {
252           ++dp->rows;
253           dp->dots = 0;
254
255           if (dp->total_length)
256             print_percentage (dp->rows * row_bytes, dp->total_length);
257
258           print_download_speed (dp,
259                                 row_bytes - (dp->initial_length % row_bytes));
260         }
261     }
262
263   log_set_flush (1);
264 }
265
266 /* Dot-progress backend for progress_finish. */
267
268 static void
269 dot_finish (void *progress)
270 {
271   struct dot_progress *dp = progress;
272   int dot_bytes = opt.dot_bytes;
273   long row_bytes = opt.dot_bytes * opt.dots_in_line;
274   int i;
275
276   log_set_flush (0);
277
278   for (i = dp->dots; i < opt.dots_in_line; i++)
279     {
280       if (i % opt.dot_spacing == 0)
281         logputs (LOG_VERBOSE, " ");
282       logputs (LOG_VERBOSE, " ");
283     }
284   if (dp->total_length)
285     {
286       print_percentage (dp->rows * row_bytes
287                         + dp->dots * dot_bytes
288                         + dp->accumulated,
289                         dp->total_length);
290     }
291
292   print_download_speed (dp, dp->dots * dot_bytes
293                         + dp->accumulated
294                         - dp->initial_length % row_bytes);
295   logputs (LOG_VERBOSE, "\n\n");
296
297   log_set_flush (0);
298
299   wtimer_delete (dp->timer);
300   xfree (dp);
301 }
302
303 /* This function interprets the progress "parameters".  For example,
304    if Wget is invoked with --progress=bar:mega, it will set the
305    "dot-style" to "mega".  Valid styles are default, binary, mega, and
306    giga.  */
307
308 static void
309 dot_set_params (const char *params)
310 {
311   if (!params)
312     return;
313
314   /* We use this to set the retrieval style.  */
315   if (!strcasecmp (params, "default"))
316     {
317       /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
318          line.  */
319       opt.dot_bytes = 1024;
320       opt.dot_spacing = 10;
321       opt.dots_in_line = 50;
322     }
323   else if (!strcasecmp (params, "binary"))
324     {
325       /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
326          (384K) in a line.  */
327       opt.dot_bytes = 8192;
328       opt.dot_spacing = 16;
329       opt.dots_in_line = 48;
330     }
331   else if (!strcasecmp (params, "mega"))
332     {
333       /* "Mega" retrieval, for retrieving very long files; each dot is
334          64K, 8 dots in a cluster, 6 clusters (3M) in a line.  */
335       opt.dot_bytes = 65536L;
336       opt.dot_spacing = 8;
337       opt.dots_in_line = 48;
338     }
339   else if (!strcasecmp (params, "giga"))
340     {
341       /* "Giga" retrieval, for retrieving very very *very* long files;
342          each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
343          line.  */
344       opt.dot_bytes = (1L << 20);
345       opt.dot_spacing = 8;
346       opt.dots_in_line = 32;
347     }
348   else
349     fprintf (stderr,
350              _("Invalid dot style specification `%s'; leaving unchanged.\n"),
351              params);
352 }
353 \f
354 /* "Thermometer" (bar) progress. */
355
356 /* Assumed screen width if we can't find the real value.  */
357 #define DEFAULT_SCREEN_WIDTH 80
358
359 /* Minimum screen width we'll try to work with.  If this is too small,
360    create_image will overflow the buffer.  */
361 #define MINIMUM_SCREEN_WIDTH 45
362
363 static int screen_width = DEFAULT_SCREEN_WIDTH;
364
365 struct bar_progress {
366   long initial_length;          /* how many bytes have been downloaded
367                                    previously. */
368   long total_length;            /* expected total byte count when the
369                                    download finishes */
370   long count;                   /* bytes downloaded so far */
371
372   struct wget_timer *timer;     /* timer used to measure the download
373                                    rates. */
374   long last_update;             /* time of the last screen update. */
375
376   int width;                    /* screen width at the time the
377                                    progress gauge was created. */
378   char *buffer;                 /* buffer where the bar "image" is
379                                    stored. */
380 };
381
382 static void create_image PARAMS ((struct bar_progress *, long));
383 static void display_image PARAMS ((char *));
384
385 static void *
386 bar_create (long initial, long total)
387 {
388   struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
389
390   memset (bp, 0, sizeof (*bp));
391
392   bp->initial_length = initial;
393   bp->total_length   = total;
394   bp->timer = wtimer_new ();
395   bp->width = screen_width;
396   bp->buffer = xmalloc (bp->width + 1);
397
398   logputs (LOG_VERBOSE, "\n");
399
400   create_image (bp, 0);
401   display_image (bp->buffer);
402
403   return bp;
404 }
405
406 static void
407 bar_update (void *progress, long howmuch)
408 {
409   struct bar_progress *bp = progress;
410   int force_update = 0;
411   long dltime = wtimer_elapsed (bp->timer);
412
413   bp->count += howmuch;
414   if (bp->count + bp->initial_length > bp->total_length)
415     /* We could be downloading more than total_length, e.g. when the
416        server sends an incorrect Content-Length header.  In that case,
417        adjust bp->total_length to the new reality, so that the code in
418        create_image() that depends on total size being smaller or
419        equal to the expected size doesn't abort.  */
420     bp->total_length = bp->count + bp->initial_length;
421
422   if (screen_width != bp->width)
423     {
424       bp->width = screen_width;
425       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
426     }
427
428   if (dltime - bp->last_update < 200 && !force_update)
429     /* Don't update more often than every half a second. */
430     return;
431
432   bp->last_update = dltime;
433
434   create_image (bp, dltime);
435   display_image (bp->buffer);
436 }
437
438 static void
439 bar_finish (void *progress)
440 {
441   struct bar_progress *bp = progress;
442
443   create_image (bp, wtimer_elapsed (bp->timer));
444   display_image (bp->buffer);
445
446   logputs (LOG_VERBOSE, "\n\n");
447
448   xfree (bp->buffer);
449   wtimer_delete (bp->timer);
450   xfree (bp);
451 }
452
453 static void
454 create_image (struct bar_progress *bp, long dltime)
455 {
456   char *p = bp->buffer;
457   long size = bp->initial_length + bp->count;
458
459   /* The progress bar should look like this:
460      xxx% |=======>             | xx KB/s nnnnn ETA: 00:00
461
462      Calculate its geometry:
463
464      "xxx% "         - percentage                - 5 chars
465      "| ... | "      - progress bar decorations  - 3 chars
466      "1012.56 K/s "  - dl rate                   - 12 chars
467      "nnnn "         - downloaded bytes          - 11 chars
468      "ETA: xx:xx:xx" - ETA                       - 13 chars
469
470      "=====>..."     - progress bar content      - the rest
471   */
472   int progress_len = screen_width - (5 + 3 + 12 + 11 + 13);
473
474   if (progress_len < 7)
475     progress_len = 0;
476
477   /* "xxx% " */
478   if (bp->total_length > 0)
479     {
480       int percentage = (int)(100.0 * size / bp->total_length);
481
482       assert (percentage <= 100);
483
484       sprintf (p, "%3d%% ", percentage);
485       p += 5;
486     }
487
488   /* The progress bar: "|====>      | " */
489   if (progress_len && bp->total_length > 0)
490     {
491       double fraction = (double)size / bp->total_length;
492       int dlsz = (int)(fraction * progress_len);
493       char *begin;
494
495       assert (dlsz <= progress_len);
496
497       *p++ = '|';
498       begin = p;
499
500       if (dlsz > 0)
501         {
502           /* Draw dlsz-1 '=' chars and one arrow char.  */
503           while (dlsz-- > 1)
504             *p++ = '=';
505           *p++ = '>';
506         }
507
508       while (p - begin < progress_len)
509         *p++ = ' ';
510
511       *p++ = '|';
512       *p++ = ' ';
513     }
514
515   /* "1012.45 K/s " */
516   if (dltime && bp->count)
517     {
518       char *rt = rate (bp->count, dltime, 1);
519       strcpy (p, rt);
520       p += strlen (p);
521       *p++ = ' ';
522     }
523   else
524     {
525       strcpy (p, "----.-- K/s ");
526       p += 12;
527     }
528
529   /* "12376 " */
530   sprintf (p, _("%ld "), size);
531   p += strlen (p);
532
533   /* "ETA: xx:xx:xx" */
534   if (bp->total_length > 0 && bp->count > 0)
535     {
536       int eta, eta_hrs, eta_min, eta_sec;
537       double tm_sofar = (double)dltime / 1000;
538       long bytes_remaining = bp->total_length - size;
539
540       eta = (int) (tm_sofar * bytes_remaining / bp->count);
541
542       eta_hrs = eta / 3600, eta %= 3600;
543       eta_min = eta / 60,   eta %= 60;
544       eta_sec = eta;
545
546       /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
547       /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
548
549       *p++ = 'E';
550       *p++ = 'T';
551       *p++ = 'A';
552       *p++ = ':';
553       *p++ = ' ';
554
555       if (eta_hrs > 99)
556         /* Bogus value, for whatever reason.  We must avoid overflow. */
557         sprintf (p, "--:--");
558       else if (eta_hrs > 0)
559         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
560       else
561         sprintf (p, "%02d:%02d", eta_min, eta_sec);
562       p += strlen (p);
563     }
564   else if (bp->total_length > 0)
565     {
566       strcpy (p, "ETA: --:--");
567       p += 10;
568     }
569
570   assert (p - bp->buffer <= screen_width);
571
572   while (p < bp->buffer + screen_width)
573     *p++ = ' ';
574   *p = '\0';
575 }
576
577 /* Print the contents of the buffer as a one-line ASCII "image" so
578    that it can be overwritten next time.  */
579
580 static void
581 display_image (char *buf)
582 {
583   int len = strlen (buf);
584   char *del_buf = alloca (len + 1);
585
586   logputs (LOG_VERBOSE, buf);
587
588   memset (del_buf, '\b', len);
589   del_buf[len] = '\0';
590
591   logputs (LOG_VERBOSE, del_buf);
592 }
593
594 static void
595 bar_set_params (const char *ignored)
596 {
597   int sw;
598
599   if (opt.lfilename
600 #ifdef HAVE_ISATTY
601       || !isatty (fileno (stderr))
602 #else
603       1
604 #endif
605       )
606     {
607       /* We're not printing to a TTY.  Revert to the fallback
608          display. */
609       set_progress_implementation (NULL);
610       return;
611     }
612
613   sw = determine_screen_width ();
614   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
615     screen_width = sw;
616 }
617
618 RETSIGTYPE
619 progress_handle_sigwinch (int sig)
620 {
621   int sw = determine_screen_width ();
622   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
623     screen_width = sw;
624 }