]> sjero.net Git - wget/blob - src/progress.c
[svn] Minor fix for the new progress bar.
[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   if (bp->count + bp->initial_length > bp->total_length)
401     /* We could be downloading more than total_length, e.g. when the
402        server sends an incorrect Content-Length header.  In that case,
403        adjust bp->total_length to the new reality, so that the code in
404        create_image() that depends on total size being smaller or
405        equal to the expected size doesn't abort.  */
406     bp->total_length = bp->count + bp->initial_length;
407
408   if (screen_width != bp->width)
409     {
410       bp->width = screen_width;
411       bp->buffer = xrealloc (bp->buffer, bp->width + 1);
412     }
413
414   if (dltime - bp->last_update < 200 && !force_update)
415     /* Don't update more often than every half a second. */
416     return;
417
418   bp->last_update = dltime;
419
420   create_image (bp, dltime);
421   display_image (bp->buffer);
422 }
423
424 static void
425 bar_finish (void *progress)
426 {
427   struct bar_progress *bp = progress;
428
429   create_image (bp, wtimer_elapsed (bp->timer));
430   display_image (bp->buffer);
431
432   logputs (LOG_VERBOSE, "\n\n");
433
434   xfree (bp->buffer);
435   wtimer_delete (bp->timer);
436   xfree (bp);
437 }
438
439 static void
440 create_image (struct bar_progress *bp, long dltime)
441 {
442   char *p = bp->buffer;
443   long size = bp->initial_length + bp->count;
444
445   /* The progress bar should look like this:
446      xxx% |=======>             | xx KB/s nnnnn ETA: 00:00
447
448      Calculate its geometry:
449
450      "xxx% "         - percentage                - 5 chars
451      "| ... | "      - progress bar decorations  - 3 chars
452      "1234.56 K/s "  - dl rate                   - 12 chars
453      "nnnn "         - downloaded bytes          - 11 chars
454      "ETA: xx:xx:xx" - ETA                       - 13 chars
455
456      "=====>..."     - progress bar content      - the rest
457   */
458   int progress_len = screen_width - (5 + 3 + 12 + 11 + 13);
459
460   if (progress_len < 7)
461     progress_len = 0;
462
463   /* "xxx% " */
464   if (bp->total_length > 0)
465     {
466       int percentage = (int)(100.0 * size / bp->total_length);
467
468       assert (percentage <= 100);
469
470       sprintf (p, "%3d%% ", percentage);
471       p += 5;
472     }
473
474   /* The progress bar: "|====>      | " */
475   if (progress_len && bp->total_length > 0)
476     {
477       double fraction = (double)size / bp->total_length;
478       int dlsz = (int)(fraction * progress_len);
479       char *begin;
480
481       assert (dlsz <= progress_len);
482
483       *p++ = '|';
484       begin = p;
485
486       if (dlsz > 0)
487         {
488           /* Draw dlsz-1 '=' chars and one arrow char.  */
489           while (dlsz-- > 1)
490             *p++ = '=';
491           *p++ = '>';
492         }
493
494       while (p - begin < progress_len)
495         *p++ = ' ';
496
497       *p++ = '|';
498       *p++ = ' ';
499     }
500
501   /* "2.3 KB/s " */
502   if (dltime && bp->count)
503     {
504       char *rt = rate (bp->count, dltime, 1);
505       strcpy (p, rt);
506       p += strlen (p);
507       *p++ = ' ';
508     }
509   else
510     {
511       strcpy (p, "----.-- KB/s ");
512       p += 13;
513     }
514
515   /* "12376 " */
516   sprintf (p, _("%ld "), size);
517   p += strlen (p);
518
519   /* "ETA: xx:xx:xx" */
520   if (bp->total_length > 0 && bp->count > 0)
521     {
522       int eta, eta_hrs, eta_min, eta_sec;
523       double tm_sofar = (double)dltime / 1000;
524       long bytes_remaining = bp->total_length - size;
525
526       eta = (int) (tm_sofar * bytes_remaining / bp->count);
527
528       eta_hrs = eta / 3600, eta %= 3600;
529       eta_min = eta / 60,   eta %= 60;
530       eta_sec = eta;
531
532       /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
533       /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
534
535       *p++ = 'E';
536       *p++ = 'T';
537       *p++ = 'A';
538       *p++ = ':';
539       *p++ = ' ';
540
541       if (eta_hrs > 99)
542         /* Bogus value, for whatever reason.  We must avoid overflow. */
543         sprintf (p, "--:--");
544       else if (eta_hrs > 0)
545         sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
546       else
547         sprintf (p, "%02d:%02d", eta_min, eta_sec);
548       p += strlen (p);
549     }
550   else if (bp->total_length > 0)
551     {
552       strcpy (p, "ETA: --:--");
553       p += 10;
554     }
555
556   assert (p - bp->buffer <= screen_width);
557
558   while (p < bp->buffer + screen_width)
559     *p++ = ' ';
560   *p = '\0';
561 }
562
563 static void
564 display_image (char *buf)
565 {
566   int len = strlen (buf);
567   char *del_buf = alloca (len + 1);
568
569   logputs (LOG_VERBOSE, buf);
570
571   memset (del_buf, '\b', len);
572   del_buf[len] = '\0';
573
574   logputs (LOG_VERBOSE, del_buf);
575 }
576
577 static void
578 bar_set_params (const char *ignored)
579 {
580   int sw = determine_screen_width ();
581   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
582     screen_width = sw;
583 }
584
585 RETSIGTYPE
586 progress_handle_sigwinch (int sig)
587 {
588   int sw = determine_screen_width ();
589   if (sw && sw >= MINIMUM_SCREEN_WIDTH)
590     screen_width = sw;
591 }