]> sjero.net Git - wget/blob - src/progress.c
9c981bd87fc6ec975ecb26f1ef9795a1c94c5caf
[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->total_length > 0
415       && bp->count + bp->initial_length > bp->total_length)
416     /* We could be downloading more than total_length, e.g. when the
417        server sends an incorrect Content-Length header.  In that case,
418        adjust bp->total_length to the new reality, so that the code in
419        create_image() that depends on total size being smaller or
420        equal to the expected size doesn't abort.  */
421     bp->total_length = bp->count + bp->initial_length;
422
423   if (screen_width != bp->width)
424     {
425       bp->width = screen_width;
426       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
427     }
428
429   if (dltime - bp->last_update < 200 && !force_update)
430     /* Don't update more often than every half a second. */
431     return;
432
433   bp->last_update = dltime;
434
435   create_image (bp, dltime);
436   display_image (bp->buffer);
437 }
438
439 static void
440 bar_finish (void *progress)
441 {
442   struct bar_progress *bp = progress;
443   long elapsed = wtimer_elapsed (bp->timer);
444
445   if (elapsed == 0)
446     /* If the download was faster than the granularity of the timer,
447        fake some output so that we don't get the ugly "----.--" rate
448        at the download finish.  */
449     elapsed = 1;
450
451   create_image (bp, elapsed);
452   display_image (bp->buffer);
453
454   logputs (LOG_VERBOSE, "\n\n");
455
456   xfree (bp->buffer);
457   wtimer_delete (bp->timer);
458   xfree (bp);
459 }
460
461 static void
462 create_image (struct bar_progress *bp, long dltime)
463 {
464   char *p = bp->buffer;
465   long size = bp->initial_length + bp->count;
466
467   /* The progress bar should look like this:
468      xxx% |=======>             | xx KB/s nnnnn ETA: 00:00
469
470      Calculate its geometry:
471
472      "xxx% "         - percentage                - 5 chars
473      "| ... | "      - progress bar decorations  - 3 chars
474      "1012.56 K/s "  - dl rate                   - 12 chars
475      "nnnn "         - downloaded bytes          - 11 chars
476      "ETA: xx:xx:xx" - ETA                       - 13 chars
477
478      "=====>..."     - progress bar content      - the rest
479   */
480   int progress_len = screen_width - (5 + 3 + 12 + 11 + 13);
481
482   if (progress_len < 7)
483     progress_len = 0;
484
485   /* "xxx% " */
486   if (bp->total_length > 0)
487     {
488       int percentage = (int)(100.0 * size / bp->total_length);
489
490       assert (percentage <= 100);
491
492       sprintf (p, "%3d%% ", percentage);
493       p += 5;
494     }
495
496   /* The progress bar: "|====>      | " */
497   if (progress_len && bp->total_length > 0)
498     {
499       double fraction = (double)size / bp->total_length;
500       int dlsz = (int)(fraction * progress_len);
501       char *begin;
502
503       assert (dlsz <= progress_len);
504
505       *p++ = '|';
506       begin = p;
507
508       if (dlsz > 0)
509         {
510           /* Draw dlsz-1 '=' chars and one arrow char.  */
511           while (dlsz-- > 1)
512             *p++ = '=';
513           *p++ = '>';
514         }
515
516       while (p - begin < progress_len)
517         *p++ = ' ';
518
519       *p++ = '|';
520       *p++ = ' ';
521     }
522
523   /* "1012.45 K/s " */
524   if (dltime && bp->count)
525     {
526       char *rt = rate (bp->count, dltime, 1);
527       strcpy (p, rt);
528       p += strlen (p);
529       *p++ = ' ';
530     }
531   else
532     {
533       strcpy (p, "----.-- K/s ");
534       p += 12;
535     }
536
537   /* "12376 " */
538   sprintf (p, "%ld ", size);
539   p += strlen (p);
540
541   /* "ETA: xx:xx:xx" */
542   if (bp->total_length > 0 && bp->count > 0)
543     {
544       int eta, eta_hrs, eta_min, eta_sec;
545       double tm_sofar = (double)dltime / 1000;
546       long bytes_remaining = bp->total_length - size;
547
548       eta = (int) (tm_sofar * bytes_remaining / bp->count);
549
550       eta_hrs = eta / 3600, eta %= 3600;
551       eta_min = eta / 60,   eta %= 60;
552       eta_sec = eta;
553
554       /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
555       /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
556
557       *p++ = 'E';
558       *p++ = 'T';
559       *p++ = 'A';
560       *p++ = ':';
561       *p++ = ' ';
562
563       if (eta_hrs > 99)
564         /* Bogus value, for whatever reason.  We must avoid overflow. */
565         sprintf (p, "--:--");
566       else if (eta_hrs > 0)
567         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
568       else
569         sprintf (p, "%02d:%02d", eta_min, eta_sec);
570       p += strlen (p);
571     }
572   else if (bp->total_length > 0)
573     {
574       strcpy (p, "ETA: --:--");
575       p += 10;
576     }
577
578   assert (p - bp->buffer <= screen_width);
579
580   while (p < bp->buffer + screen_width)
581     *p++ = ' ';
582   *p = '\0';
583 }
584
585 /* Print the contents of the buffer as a one-line ASCII "image" so
586    that it can be overwritten next time.  */
587
588 static void
589 display_image (char *buf)
590 {
591   int len = strlen (buf);
592   char *del_buf = alloca (len + 1);
593
594   logputs (LOG_VERBOSE, buf);
595
596   memset (del_buf, '\b', len);
597   del_buf[len] = '\0';
598
599   logputs (LOG_VERBOSE, del_buf);
600 }
601
602 static void
603 bar_set_params (const char *params)
604 {
605   int sw;
606
607   if ((opt.lfilename
608 #ifdef HAVE_ISATTY
609        || !isatty (fileno (stderr))
610 #else
611        1
612 #endif
613        )
614       && !(params != NULL
615            && 0 == strcmp (params, "force")))
616     {
617       /* We're not printing to a TTY, so revert to the fallback
618          display.  #### We're recursively calling
619          set_progress_implementation here, which is slightly kludgy.
620          It would be nicer if that function could resolve this problem
621          itself.  */
622       set_progress_implementation (NULL);
623       return;
624     }
625
626   sw = determine_screen_width ();
627   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
628     screen_width = sw;
629 }
630
631 RETSIGTYPE
632 progress_handle_sigwinch (int sig)
633 {
634   int sw = determine_screen_width ();
635   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
636     screen_width = sw;
637 }