2 Copyright (C) 2001 Free Software Foundation, Inc.
4 This file is part of GNU Wget.
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.
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.
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. */
28 #endif /* HAVE_STRING_H */
42 struct progress_implementation {
44 void *(*create) (long, long);
45 void (*update) (void *, long, long);
46 void (*finish) (void *, long);
47 void (*set_params) (const char *);
50 /* Necessary forward declarations. */
52 static void *dot_create PARAMS ((long, long));
53 static void dot_update PARAMS ((void *, long, long));
54 static void dot_finish PARAMS ((void *, long));
55 static void dot_set_params PARAMS ((const char *));
57 static void *bar_create PARAMS ((long, long));
58 static void bar_update PARAMS ((void *, long, long));
59 static void bar_finish PARAMS ((void *, long));
60 static void bar_set_params PARAMS ((const char *));
62 static struct progress_implementation implementations[] = {
63 { "dot", dot_create, dot_update, dot_finish, dot_set_params },
64 { "bar", bar_create, bar_update, bar_finish, bar_set_params }
66 static struct progress_implementation *current_impl;
67 static int current_impl_locked;
69 /* Progress implementation used by default. Can be overriden in
70 wgetrc or by the fallback one. */
72 #define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
74 /* Fallnback progress implementation should be something that works
75 under all display types. If you put something other than "dot"
76 here, remember that bar_set_params tries to switch to this if we're
77 not running on a TTY. So changing this to "bar" could cause
80 #define FALLBACK_PROGRESS_IMPLEMENTATION "dot"
82 /* Return non-zero if NAME names a valid progress bar implementation.
83 The characters after the first : will be ignored. */
86 valid_progress_implementation_p (const char *name)
89 struct progress_implementation *pi = implementations;
90 char *colon = strchr (name, ':');
91 int namelen = colon ? colon - name : strlen (name);
93 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
94 if (!strncmp (pi->name, name, namelen))
99 /* Set the progress implementation to NAME. */
102 set_progress_implementation (const char *name)
105 struct progress_implementation *pi = implementations;
109 name = DEFAULT_PROGRESS_IMPLEMENTATION;
111 colon = strchr (name, ':');
112 namelen = colon ? colon - name : strlen (name);
114 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
115 if (!strncmp (pi->name, name, namelen))
118 current_impl_locked = 0;
121 /* We call pi->set_params even if colon is NULL because we
122 want to give the implementation a chance to set up some
123 things it needs to run. */
127 pi->set_params (colon);
133 static int output_redirected;
136 progress_schedule_redirect (void)
138 output_redirected = 1;
141 /* Create a progress gauge. INITIAL is the number of bytes the
142 download starts from (zero if the download starts from scratch).
143 TOTAL is the expected total number of bytes in this download. If
144 TOTAL is zero, it means that the download size is not known in
148 progress_create (long initial, long total)
150 /* Check if the log status has changed under our feet. */
151 if (output_redirected)
153 if (!current_impl_locked)
154 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
155 output_redirected = 0;
158 return current_impl->create (initial, total);
161 /* Inform the progress gauge of newly received bytes. DLTIME is the
162 time in milliseconds since the beginning of the download. */
165 progress_update (void *progress, long howmuch, long dltime)
167 current_impl->update (progress, howmuch, dltime);
170 /* Tell the progress gauge to clean up. Calling this will free the
171 PROGRESS object, the further use of which is not allowed. */
174 progress_finish (void *progress, long dltime)
176 current_impl->finish (progress, dltime);
181 struct dot_progress {
182 long initial_length; /* how many bytes have been downloaded
184 long total_length; /* expected total byte count when the
189 int rows; /* number of rows printed so far */
190 int dots; /* number of dots printed in this row */
191 long last_timer_value;
194 /* Dot-progress backend for progress_create. */
197 dot_create (long initial, long total)
199 struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
201 memset (dp, 0, sizeof (*dp));
203 dp->initial_length = initial;
204 dp->total_length = total;
206 if (dp->initial_length)
208 int dot_bytes = opt.dot_bytes;
209 long row_bytes = opt.dot_bytes * opt.dots_in_line;
211 int remainder = (int) (dp->initial_length % row_bytes);
212 long skipped = dp->initial_length - remainder;
216 int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
217 int skipped_k_len = numdigit (skipped_k);
218 if (skipped_k_len < 5)
221 /* Align the [ skipping ... ] line with the dots. To do
222 that, insert the number of spaces equal to the number of
223 digits in the skipped amount in K. */
224 logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"),
225 2 + skipped_k_len, "", skipped_k);
228 logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
229 for (; remainder >= dot_bytes; remainder -= dot_bytes)
231 if (dp->dots % opt.dot_spacing == 0)
232 logputs (LOG_VERBOSE, " ");
233 logputs (LOG_VERBOSE, ",");
236 assert (dp->dots < opt.dots_in_line);
238 dp->accumulated = remainder;
239 dp->rows = skipped / row_bytes;
246 print_percentage (long bytes, long expected)
248 int percentage = (int)(100.0 * bytes / expected);
249 logprintf (LOG_VERBOSE, "%3d%%", percentage);
253 print_download_speed (struct dot_progress *dp, long bytes, long dltime)
255 logprintf (LOG_VERBOSE, " %s",
256 retr_rate (bytes, dltime - dp->last_timer_value, 1));
257 dp->last_timer_value = dltime;
260 /* Dot-progress backend for progress_update. */
263 dot_update (void *progress, long howmuch, long dltime)
265 struct dot_progress *dp = progress;
266 int dot_bytes = opt.dot_bytes;
267 long row_bytes = opt.dot_bytes * opt.dots_in_line;
271 dp->accumulated += howmuch;
272 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
275 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
277 if (dp->dots % opt.dot_spacing == 0)
278 logputs (LOG_VERBOSE, " ");
279 logputs (LOG_VERBOSE, ".");
282 if (dp->dots >= opt.dots_in_line)
284 long row_qty = row_bytes;
285 if (dp->rows == dp->initial_length / row_bytes)
286 row_qty -= dp->initial_length % row_bytes;
291 if (dp->total_length)
292 print_percentage (dp->rows * row_bytes, dp->total_length);
293 print_download_speed (dp, row_qty, dltime);
300 /* Dot-progress backend for progress_finish. */
303 dot_finish (void *progress, long dltime)
305 struct dot_progress *dp = progress;
306 int dot_bytes = opt.dot_bytes;
307 long row_bytes = opt.dot_bytes * opt.dots_in_line;
313 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
314 for (i = dp->dots; i < opt.dots_in_line; i++)
316 if (i % opt.dot_spacing == 0)
317 logputs (LOG_VERBOSE, " ");
318 logputs (LOG_VERBOSE, " ");
320 if (dp->total_length)
322 print_percentage (dp->rows * row_bytes
323 + dp->dots * dot_bytes
329 long row_qty = dp->dots * dot_bytes + dp->accumulated;
330 if (dp->rows == dp->initial_length / row_bytes)
331 row_qty -= dp->initial_length % row_bytes;
332 print_download_speed (dp, row_qty, dltime);
335 logputs (LOG_VERBOSE, "\n\n");
341 /* This function interprets the progress "parameters". For example,
342 if Wget is invoked with --progress=dot:mega, it will set the
343 "dot-style" to "mega". Valid styles are default, binary, mega, and
347 dot_set_params (const char *params)
349 if (!params || !*params)
350 params = opt.dot_style;
355 /* We use this to set the retrieval style. */
356 if (!strcasecmp (params, "default"))
358 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
360 opt.dot_bytes = 1024;
361 opt.dot_spacing = 10;
362 opt.dots_in_line = 50;
364 else if (!strcasecmp (params, "binary"))
366 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
368 opt.dot_bytes = 8192;
369 opt.dot_spacing = 16;
370 opt.dots_in_line = 48;
372 else if (!strcasecmp (params, "mega"))
374 /* "Mega" retrieval, for retrieving very long files; each dot is
375 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
376 opt.dot_bytes = 65536L;
378 opt.dots_in_line = 48;
380 else if (!strcasecmp (params, "giga"))
382 /* "Giga" retrieval, for retrieving very very *very* long files;
383 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
385 opt.dot_bytes = (1L << 20);
387 opt.dots_in_line = 32;
391 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
395 /* "Thermometer" (bar) progress. */
397 /* Assumed screen width if we can't find the real value. */
398 #define DEFAULT_SCREEN_WIDTH 80
400 /* Minimum screen width we'll try to work with. If this is too small,
401 create_image will overflow the buffer. */
402 #define MINIMUM_SCREEN_WIDTH 45
404 /* Number of recent packets we keep the stats for. */
405 #define RECENT_ARRAY_SIZE 30
407 static int screen_width = DEFAULT_SCREEN_WIDTH;
409 struct bar_progress {
410 long initial_length; /* how many bytes have been downloaded
412 long total_length; /* expected total byte count when the
414 long count; /* bytes downloaded so far */
416 long last_screen_update; /* time of the last screen update. */
418 int width; /* screen width we're using at the
419 time the progress gauge was
420 created. this is different from
421 the screen_width global variable in
422 that the latter can be changed by a
424 char *buffer; /* buffer where the bar "image" is
426 int tick; /* counter used for drawing the
427 progress bar where the total size
430 /* The following variables (kept in a struct for namespace reasons)
431 keep track of how long it took to read recent packets. See
432 bar_update() for explanation. */
435 long times[RECENT_ARRAY_SIZE];
436 long bytes[RECENT_ARRAY_SIZE];
442 /* create_image() uses these to make sure that ETA information
444 long last_eta_time; /* time of the last update to download
449 static void create_image PARAMS ((struct bar_progress *, long));
450 static void display_image PARAMS ((char *));
453 bar_create (long initial, long total)
455 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
457 memset (bp, 0, sizeof (*bp));
459 bp->initial_length = initial;
460 bp->total_length = total;
462 /* - 1 because we don't want to use the last screen column. */
463 bp->width = screen_width - 1;
464 /* + 1 for the terminating zero. */
465 bp->buffer = xmalloc (bp->width + 1);
467 logputs (LOG_VERBOSE, "\n");
469 create_image (bp, 0);
470 display_image (bp->buffer);
476 bar_update (void *progress, long howmuch, long dltime)
478 struct bar_progress *bp = progress;
479 int force_screen_update = 0;
482 bp->count += howmuch;
483 if (bp->total_length > 0
484 && bp->count + bp->initial_length > bp->total_length)
485 /* We could be downloading more than total_length, e.g. when the
486 server sends an incorrect Content-Length header. In that case,
487 adjust bp->total_length to the new reality, so that the code in
488 create_image() that depends on total size being smaller or
489 equal to the expected size doesn't abort. */
490 bp->total_length = bp->count + bp->initial_length;
492 /* The progress bar is supposed to display the "current download
493 speed". The first version of the progress bar calculated it by
494 dividing the total amount of data with the total time needed to
495 download it. The problem with this was that stalled or suspended
496 download could unduly influence the "current" time. Taking just
497 the time needed to download the current packet would not work
498 either because packets arrive too fast and the varitions would be
501 It would be preferrable to show the speed that pertains to a
502 recent period, say over the past several seconds. But to do this
503 accurately, we would have to record all the packets received
504 during the last five seconds.
506 What we do instead is maintain a history of a fixed number of
507 packets. It actually makes sense if you think about it -- faster
508 downloads will have a faster response to speed changes. */
510 rec_index = bp->recent.count % RECENT_ARRAY_SIZE;
513 /* Instead of calculating the sum of times[] and bytes[], we
514 maintain the summed quantities. To maintain each sum, we must
515 make sure that it gets increased by the newly downloaded amount,
516 but also that it gets decreased by the amount we're overwriting
517 in (erasing from) the cyclical buffer. */
518 bp->recent.summed_times -= bp->recent.times[rec_index];
519 bp->recent.summed_bytes -= bp->recent.bytes[rec_index];
521 bp->recent.times[rec_index] = dltime - bp->recent.previous_time;
522 bp->recent.bytes[rec_index] = howmuch;
524 bp->recent.summed_times += bp->recent.times[rec_index];
525 bp->recent.summed_bytes += bp->recent.bytes[rec_index];
527 bp->recent.previous_time = dltime;
530 /* Sledgehammer check that summed_times and summed_bytes are
533 int num = bp->recent.count;
535 int upper = num < RECENT_ARRAY_SIZE ? num : RECENT_ARRAY_SIZE;
536 long sumt = 0, sumb = 0;
537 for (i = 0; i < upper; i++)
539 sumt += bp->recent.times[i];
540 sumb += bp->recent.bytes[i];
542 assert (sumt == bp->recent.summed_times);
543 assert (sumb == bp->recent.summed_bytes);
547 if (screen_width - 1 != bp->width)
549 bp->width = screen_width - 1;
550 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
551 force_screen_update = 1;
554 if (dltime - bp->last_screen_update < 200 && !force_screen_update)
555 /* Don't update more often than five times per second. */
558 create_image (bp, dltime);
559 display_image (bp->buffer);
560 bp->last_screen_update = dltime;
564 bar_finish (void *progress, long dltime)
566 struct bar_progress *bp = progress;
569 /* If the download was faster than the granularity of the timer,
570 fake some output so that we don't get the ugly "----.--" rate
571 at the download finish. */
574 create_image (bp, dltime);
575 display_image (bp->buffer);
577 logputs (LOG_VERBOSE, "\n\n");
583 #define APPEND_LITERAL(s) do { \
584 memcpy (p, s, sizeof (s) - 1); \
585 p += sizeof (s) - 1; \
589 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
593 create_image (struct bar_progress *bp, long dl_total_time)
595 char *p = bp->buffer;
596 long size = bp->initial_length + bp->count;
598 char *size_legible = legible (size);
599 int size_legible_len = strlen (size_legible);
601 long recent_time = bp->recent.summed_times;
602 long recent_bytes = bp->recent.summed_bytes;
604 /* The progress bar should look like this:
605 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
607 Calculate the geometry. The idea is to assign as much room as
608 possible to the progress bar. The other idea is to never let
609 things "jitter", i.e. pad elements that vary in size so that
610 their variance does not affect the placement of other elements.
611 It would be especially bad for the progress bar to be resized
614 "xx% " or "100%" - percentage - 4 chars
615 "[]" - progress bar decorations - 2 chars
616 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
617 " 1012.56K/s" - dl rate - 11 chars
618 " ETA xx:xx:xx" - ETA - 13 chars
620 "=====>..." - progress bar - the rest
622 int dlbytes_size = 1 + MAX (size_legible_len, 11);
623 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
625 if (progress_size < 5)
629 if (bp->total_length > 0)
631 int percentage = (int)(100.0 * size / bp->total_length);
633 assert (percentage <= 100);
635 if (percentage < 100)
636 sprintf (p, "%2d%% ", percentage);
642 APPEND_LITERAL (" ");
644 /* The progress bar: "[====> ]" */
645 if (progress_size && bp->total_length > 0)
647 double fraction = (double)size / bp->total_length;
648 int dlsz = (int)(fraction * progress_size);
651 assert (dlsz <= progress_size);
658 /* Draw dlsz-1 '=' chars and one arrow char. */
664 while (p - begin < progress_size)
669 else if (progress_size)
671 /* If we can't draw a real progress bar, then at least show
672 *something* to the user. */
673 int ind = bp->tick % (progress_size * 2 - 6);
676 /* Make the star move in two directions. */
677 if (ind < progress_size - 2)
680 pos = progress_size - (ind - progress_size + 5);
683 for (i = 0; i < progress_size; i++)
685 if (i == pos - 1) *p++ = '<';
686 else if (i == pos ) *p++ = '=';
687 else if (i == pos + 1) *p++ = '>';
697 sprintf (p, " %-11s", legible (size));
701 if (recent_time && recent_bytes)
703 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
705 double dlrate = calc_rate (recent_bytes, recent_time, &units);
706 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
710 APPEND_LITERAL (" --.--K/s");
712 /* " ETA xx:xx:xx" */
713 if (bp->total_length > 0 && recent_bytes > 0)
716 int eta_hrs, eta_min, eta_sec;
718 /* Don't change the value of ETA more than approximately once
719 per second; doing so would cause flashing without providing
720 any value to the user. */
721 if (dl_total_time - bp->last_eta_time < 900
722 && bp->last_eta_value != 0)
723 eta = bp->last_eta_value;
726 double tm_sofar = (double)recent_time / 1000;
727 long bytes_remaining = bp->total_length - size;
728 eta = (long) (tm_sofar * bytes_remaining / recent_bytes);
729 bp->last_eta_value = eta;
730 bp->last_eta_time = dl_total_time;
733 eta_hrs = eta / 3600, eta %= 3600;
734 eta_min = eta / 60, eta %= 60;
737 /* Pad until the end of screen. The padding is dependent on the
739 if (eta_hrs == 0 || eta_hrs > 99)
740 /* Hours not printed: pad with three spaces (two digits and
742 APPEND_LITERAL (" ");
743 else if (eta_hrs < 10)
744 /* Hours printed with one digit: pad with one space. */
747 /* Hours printed with two digits: we're using maximum width,
751 APPEND_LITERAL (" ETA ");
754 /* Bogus value, probably due to a calculation overflow. Print
755 something safe to avoid overstepping the buffer bounds. */
756 sprintf (p, "--:--");
757 else if (eta_hrs > 0)
758 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
760 sprintf (p, "%02d:%02d", eta_min, eta_sec);
763 else if (bp->total_length > 0)
764 APPEND_LITERAL (" ETA --:--");
766 assert (p - bp->buffer <= bp->width);
768 while (p < bp->buffer + bp->width)
773 /* Print the contents of the buffer as a one-line ASCII "image" so
774 that it can be overwritten next time. */
777 display_image (char *buf)
779 int old = log_set_save_context (0);
780 logputs (LOG_VERBOSE, "\r");
781 logputs (LOG_VERBOSE, buf);
782 log_set_save_context (old);
786 bar_set_params (const char *params)
791 && 0 == strcmp (params, "force"))
792 current_impl_locked = 1;
796 || !isatty (fileno (stderr))
801 && !current_impl_locked)
803 /* We're not printing to a TTY, so revert to the fallback
804 display. #### We're recursively calling
805 set_progress_implementation here, which is slightly kludgy.
806 It would be nicer if we provided that function a return value
807 indicating a failure of some sort. */
808 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
812 sw = determine_screen_width ();
813 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
819 progress_handle_sigwinch (int sig)
821 int sw = determine_screen_width ();
822 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
824 signal (SIGWINCH, progress_handle_sigwinch);