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