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