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 create_image (bp, dltime);
569 display_image (bp->buffer);
571 logputs (LOG_VERBOSE, "\n\n");
577 #define APPEND_LITERAL(s) do { \
578 memcpy (p, s, sizeof (s) - 1); \
579 p += sizeof (s) - 1; \
583 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
587 create_image (struct bar_progress *bp, long dl_total_time)
589 char *p = bp->buffer;
590 long size = bp->initial_length + bp->count;
592 char *size_legible = legible (size);
593 int size_legible_len = strlen (size_legible);
595 struct bar_progress_hist *hist = &bp->hist;
597 /* The progress bar should look like this:
598 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
600 Calculate the geometry. The idea is to assign as much room as
601 possible to the progress bar. The other idea is to never let
602 things "jitter", i.e. pad elements that vary in size so that
603 their variance does not affect the placement of other elements.
604 It would be especially bad for the progress bar to be resized
607 "xx% " or "100%" - percentage - 4 chars
608 "[]" - progress bar decorations - 2 chars
609 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
610 " 1012.56K/s" - dl rate - 11 chars
611 " ETA xx:xx:xx" - ETA - 13 chars
613 "=====>..." - progress bar - the rest
615 int dlbytes_size = 1 + MAX (size_legible_len, 11);
616 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
618 if (progress_size < 5)
622 if (bp->total_length > 0)
624 int percentage = (int)(100.0 * size / bp->total_length);
626 assert (percentage <= 100);
628 if (percentage < 100)
629 sprintf (p, "%2d%% ", percentage);
635 APPEND_LITERAL (" ");
637 /* The progress bar: "[====> ]" */
638 if (progress_size && bp->total_length > 0)
640 double fraction = (double)size / bp->total_length;
641 int dlsz = (int)(fraction * progress_size);
644 assert (dlsz <= progress_size);
651 /* Draw dlsz-1 '=' chars and one arrow char. */
657 while (p - begin < progress_size)
662 else if (progress_size)
664 /* If we can't draw a real progress bar, then at least show
665 *something* to the user. */
666 int ind = bp->tick % (progress_size * 2 - 6);
669 /* Make the star move in two directions. */
670 if (ind < progress_size - 2)
673 pos = progress_size - (ind - progress_size + 5);
676 for (i = 0; i < progress_size; i++)
678 if (i == pos - 1) *p++ = '<';
679 else if (i == pos ) *p++ = '=';
680 else if (i == pos + 1) *p++ = '>';
690 sprintf (p, " %-11s", legible (size));
694 if (hist->summed_times && hist->summed_bytes)
696 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
699 dlrate = calc_rate (hist->summed_bytes, hist->summed_times, &units);
700 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
704 APPEND_LITERAL (" --.--K/s");
706 /* " ETA xx:xx:xx" */
707 if (bp->total_length > 0 && dl_total_time > 3000)
710 int eta_hrs, eta_min, eta_sec;
712 /* Don't change the value of ETA more than approximately once
713 per second; doing so would cause flashing without providing
714 any value to the user. */
715 if (dl_total_time - bp->last_eta_time < 900
716 && bp->last_eta_value != 0)
717 eta = bp->last_eta_value;
720 /* Calculate ETA using the average download speed to predict
721 the future speed. If you want to use the current speed
722 instead, replace dl_total_time with hist->summed_times
723 and bp->count with hist->summed_bytes. I found that
724 doing that results in a very jerky and ultimately
726 double time_sofar = (double)dl_total_time / 1000;
727 long bytes_remaining = bp->total_length - size;
728 eta = (long) (time_sofar * bytes_remaining / bp->count);
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;
742 /* Hours not printed: pad with three spaces. */
743 APPEND_LITERAL (" ");
744 sprintf (p, " ETA %02d:%02d", eta_min, eta_sec);
749 /* Hours printed with one digit: pad with one space. */
751 sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec);
755 else if (bp->total_length > 0)
758 APPEND_LITERAL (" ");
761 assert (p - bp->buffer <= bp->width);
763 while (p < bp->buffer + bp->width)
768 /* Print the contents of the buffer as a one-line ASCII "image" so
769 that it can be overwritten next time. */
772 display_image (char *buf)
774 int old = log_set_save_context (0);
775 logputs (LOG_VERBOSE, "\r");
776 logputs (LOG_VERBOSE, buf);
777 log_set_save_context (old);
781 bar_set_params (const char *params)
786 && 0 == strcmp (params, "force"))
787 current_impl_locked = 1;
791 || !isatty (fileno (stderr))
796 && !current_impl_locked)
798 /* We're not printing to a TTY, so revert to the fallback
799 display. #### We're recursively calling
800 set_progress_implementation here, which is slightly kludgy.
801 It would be nicer if we provided that function a return value
802 indicating a failure of some sort. */
803 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
807 sw = determine_screen_width ();
808 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
814 progress_handle_sigwinch (int sig)
816 int sw = determine_screen_width ();
817 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
819 signal (SIGWINCH, progress_handle_sigwinch);