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