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 static int screen_width = DEFAULT_SCREEN_WIDTH;
406 /* Size of the history table for download speeds. */
407 #define DLSPEED_HISTORY_SIZE 30
409 /* The time interval in milliseconds below which we increase old
410 history entries rather than overwriting them. That interval
411 represents the scope of the download speed history. */
412 #define DLSPEED_HISTORY_MAX_INTERVAL 3000
414 struct bar_progress {
415 long initial_length; /* how many bytes have been downloaded
417 long total_length; /* expected total byte count when the
419 long count; /* bytes downloaded so far */
421 long last_screen_update; /* time of the last screen update. */
423 int width; /* screen width we're using at the
424 time the progress gauge was
425 created. this is different from
426 the screen_width global variable in
427 that the latter can be changed by a
429 char *buffer; /* buffer where the bar "image" is
431 int tick; /* counter used for drawing the
432 progress bar where the total size
435 /* The following variables (kept in a struct for namespace reasons)
436 keep track of recent download speeds. See bar_update() for
438 struct bar_progress_hist {
440 long times[DLSPEED_HISTORY_SIZE];
441 long bytes[DLSPEED_HISTORY_SIZE];
447 /* create_image() uses these to make sure that ETA information
449 long last_eta_time; /* time of the last update to download
454 static void create_image PARAMS ((struct bar_progress *, long));
455 static void display_image PARAMS ((char *));
458 bar_create (long initial, long total)
460 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
462 memset (bp, 0, sizeof (*bp));
464 bp->initial_length = initial;
465 bp->total_length = total;
467 /* - 1 because we don't want to use the last screen column. */
468 bp->width = screen_width - 1;
469 /* + 1 for the terminating zero. */
470 bp->buffer = xmalloc (bp->width + 1);
472 logputs (LOG_VERBOSE, "\n");
474 create_image (bp, 0);
475 display_image (bp->buffer);
481 bar_update (void *progress, long howmuch, long dltime)
483 struct bar_progress *bp = progress;
484 struct bar_progress_hist *hist = &bp->hist;
485 int force_screen_update = 0;
486 long delta_time = dltime - hist->previous_time;
488 bp->count += howmuch;
489 if (bp->total_length > 0
490 && bp->count + bp->initial_length > bp->total_length)
491 /* We could be downloading more than total_length, e.g. when the
492 server sends an incorrect Content-Length header. In that case,
493 adjust bp->total_length to the new reality, so that the code in
494 create_image() that depends on total size being smaller or
495 equal to the expected size doesn't abort. */
496 bp->total_length = bp->count + bp->initial_length;
498 /* This code attempts to determine the current download speed. We
499 measure the speed over the interval of approximately three
500 seconds, in subintervals no smaller than 0.1s. In other words,
501 we maintain and use the history of 30 most recent reads, where a
502 "read" consists of one or more network reads, up until the point
503 where a subinterval is filled. */
505 if (hist->times[hist->pos]
506 >= DLSPEED_HISTORY_MAX_INTERVAL / DLSPEED_HISTORY_SIZE)
508 /* The subinterval at POS has been used up. Move on to the next
510 if (++hist->pos == DLSPEED_HISTORY_SIZE)
513 /* Invalidate old data (from the previous cycle) at this
515 hist->summed_times -= hist->times[hist->pos];
516 hist->summed_bytes -= hist->bytes[hist->pos];
517 hist->times[hist->pos] = delta_time;
518 hist->bytes[hist->pos] = howmuch;
522 /* Increment the data at POS. */
523 hist->times[hist->pos] += delta_time;
524 hist->bytes[hist->pos] += howmuch;
527 hist->summed_times += delta_time;
528 hist->summed_bytes += howmuch;
529 hist->previous_time = dltime;
532 /* Sledgehammer check that summed_times and summed_bytes are
536 long sumt = 0, sumb = 0;
537 for (i = 0; i < DLSPEED_HISTORY_SIZE; i++)
539 sumt += hist->times[i];
540 sumb += hist->bytes[i];
542 assert (sumt == hist->summed_times);
543 assert (sumb == hist->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;
568 /* If the download was faster than the granularity of the timer,
569 fake some output so that we don't get the ugly "----.--" rate at
570 the download finish. */
571 if (bp->hist.summed_times == 0)
572 bp->hist.summed_times = 1;
576 create_image (bp, dltime);
577 display_image (bp->buffer);
579 logputs (LOG_VERBOSE, "\n\n");
585 #define APPEND_LITERAL(s) do { \
586 memcpy (p, s, sizeof (s) - 1); \
587 p += sizeof (s) - 1; \
591 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
595 create_image (struct bar_progress *bp, long dl_total_time)
597 char *p = bp->buffer;
598 long size = bp->initial_length + bp->count;
600 char *size_legible = legible (size);
601 int size_legible_len = strlen (size_legible);
603 long recent_time = bp->hist.summed_times;
604 long recent_bytes = bp->hist.summed_bytes;
606 /* The progress bar should look like this:
607 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
609 Calculate the geometry. The idea is to assign as much room as
610 possible to the progress bar. The other idea is to never let
611 things "jitter", i.e. pad elements that vary in size so that
612 their variance does not affect the placement of other elements.
613 It would be especially bad for the progress bar to be resized
616 "xx% " or "100%" - percentage - 4 chars
617 "[]" - progress bar decorations - 2 chars
618 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
619 " 1012.56K/s" - dl rate - 11 chars
620 " ETA xx:xx:xx" - ETA - 13 chars
622 "=====>..." - progress bar - the rest
624 int dlbytes_size = 1 + MAX (size_legible_len, 11);
625 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
627 if (progress_size < 5)
631 if (bp->total_length > 0)
633 int percentage = (int)(100.0 * size / bp->total_length);
635 assert (percentage <= 100);
637 if (percentage < 100)
638 sprintf (p, "%2d%% ", percentage);
644 APPEND_LITERAL (" ");
646 /* The progress bar: "[====> ]" */
647 if (progress_size && bp->total_length > 0)
649 double fraction = (double)size / bp->total_length;
650 int dlsz = (int)(fraction * progress_size);
653 assert (dlsz <= progress_size);
660 /* Draw dlsz-1 '=' chars and one arrow char. */
666 while (p - begin < progress_size)
671 else if (progress_size)
673 /* If we can't draw a real progress bar, then at least show
674 *something* to the user. */
675 int ind = bp->tick % (progress_size * 2 - 6);
678 /* Make the star move in two directions. */
679 if (ind < progress_size - 2)
682 pos = progress_size - (ind - progress_size + 5);
685 for (i = 0; i < progress_size; i++)
687 if (i == pos - 1) *p++ = '<';
688 else if (i == pos ) *p++ = '=';
689 else if (i == pos + 1) *p++ = '>';
699 sprintf (p, " %-11s", legible (size));
703 if (recent_time && recent_bytes)
705 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
707 double dlrate = calc_rate (recent_bytes, recent_time, &units);
708 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
712 APPEND_LITERAL (" --.--K/s");
714 /* " ETA xx:xx:xx" */
715 if (bp->total_length > 0 && recent_bytes > 0)
718 int eta_hrs, eta_min, eta_sec;
720 /* Don't change the value of ETA more than approximately once
721 per second; doing so would cause flashing without providing
722 any value to the user. */
723 if (dl_total_time - bp->last_eta_time < 900
724 && bp->last_eta_value != 0)
725 eta = bp->last_eta_value;
728 double tm_sofar = (double)recent_time / 1000;
729 long bytes_remaining = bp->total_length - size;
730 eta = (long) (tm_sofar * bytes_remaining / recent_bytes);
731 bp->last_eta_value = eta;
732 bp->last_eta_time = dl_total_time;
735 eta_hrs = eta / 3600, eta %= 3600;
736 eta_min = eta / 60, eta %= 60;
739 /* Pad until the end of screen. The padding is dependent on the
741 if (eta_hrs == 0 || eta_hrs > 99)
742 /* Hours not printed: pad with three spaces (two digits and
744 APPEND_LITERAL (" ");
745 else if (eta_hrs < 10)
746 /* Hours printed with one digit: pad with one space. */
749 /* Hours printed with two digits: we're using maximum width,
753 APPEND_LITERAL (" ETA ");
756 /* Bogus value, probably due to a calculation overflow. Print
757 something safe to avoid overstepping the buffer bounds. */
758 sprintf (p, "--:--");
759 else if (eta_hrs > 0)
760 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
762 sprintf (p, "%02d:%02d", eta_min, eta_sec);
765 else if (bp->total_length > 0)
766 APPEND_LITERAL (" ETA --:--");
768 assert (p - bp->buffer <= bp->width);
770 while (p < bp->buffer + bp->width)
775 /* Print the contents of the buffer as a one-line ASCII "image" so
776 that it can be overwritten next time. */
779 display_image (char *buf)
781 int old = log_set_save_context (0);
782 logputs (LOG_VERBOSE, "\r");
783 logputs (LOG_VERBOSE, buf);
784 log_set_save_context (old);
788 bar_set_params (const char *params)
793 && 0 == strcmp (params, "force"))
794 current_impl_locked = 1;
798 || !isatty (fileno (stderr))
803 && !current_impl_locked)
805 /* We're not printing to a TTY, so revert to the fallback
806 display. #### We're recursively calling
807 set_progress_implementation here, which is slightly kludgy.
808 It would be nicer if we provided that function a return value
809 indicating a failure of some sort. */
810 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
814 sw = determine_screen_width ();
815 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
821 progress_handle_sigwinch (int sig)
823 int sw = determine_screen_width ();
824 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
826 signal (SIGWINCH, progress_handle_sigwinch);