2 Copyright (C) 2001, 2002 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) PARAMS ((long, long));
45 void (*update) PARAMS ((void *, long, long));
46 void (*finish) PARAMS ((void *, long));
47 void (*set_params) 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 /* In theory, our callers should take care of this pathological
465 case, but it can sometimes happen. */
469 bp->initial_length = initial;
470 bp->total_length = total;
472 /* - 1 because we don't want to use the last screen column. */
473 bp->width = screen_width - 1;
474 /* + 1 for the terminating zero. */
475 bp->buffer = xmalloc (bp->width + 1);
477 logputs (LOG_VERBOSE, "\n");
479 create_image (bp, 0);
480 display_image (bp->buffer);
486 bar_update (void *progress, long howmuch, long dltime)
488 struct bar_progress *bp = progress;
489 struct bar_progress_hist *hist = &bp->hist;
490 int force_screen_update = 0;
491 long delta_time = dltime - hist->previous_time;
493 bp->count += howmuch;
494 if (bp->total_length > 0
495 && bp->count + bp->initial_length > bp->total_length)
496 /* We could be downloading more than total_length, e.g. when the
497 server sends an incorrect Content-Length header. In that case,
498 adjust bp->total_length to the new reality, so that the code in
499 create_image() that depends on total size being smaller or
500 equal to the expected size doesn't abort. */
501 bp->total_length = bp->initial_length + bp->count;
503 /* This code attempts to determine the current download speed. We
504 measure the speed over the interval of approximately three
505 seconds, in subintervals no smaller than 0.1s. In other words,
506 we maintain and use the history of 30 most recent reads, where a
507 "read" consists of one or more network reads, up until the point
508 where a subinterval is filled. */
510 if (hist->times[hist->pos]
511 >= DLSPEED_HISTORY_MAX_INTERVAL / DLSPEED_HISTORY_SIZE)
513 /* The subinterval at POS has been used up. Move on to the next
515 if (++hist->pos == DLSPEED_HISTORY_SIZE)
518 /* Invalidate old data (from the previous cycle) at this
520 hist->summed_times -= hist->times[hist->pos];
521 hist->summed_bytes -= hist->bytes[hist->pos];
522 hist->times[hist->pos] = delta_time;
523 hist->bytes[hist->pos] = howmuch;
527 /* Increment the data at POS. */
528 hist->times[hist->pos] += delta_time;
529 hist->bytes[hist->pos] += howmuch;
532 hist->summed_times += delta_time;
533 hist->summed_bytes += howmuch;
534 hist->previous_time = dltime;
537 /* Sledgehammer check that summed_times and summed_bytes are
541 long sumt = 0, sumb = 0;
542 for (i = 0; i < DLSPEED_HISTORY_SIZE; i++)
544 sumt += hist->times[i];
545 sumb += hist->bytes[i];
547 assert (sumt == hist->summed_times);
548 assert (sumb == hist->summed_bytes);
552 if (screen_width - 1 != bp->width)
554 bp->width = screen_width - 1;
555 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
556 force_screen_update = 1;
559 if (dltime - bp->last_screen_update < 200 && !force_screen_update)
560 /* Don't update more often than five times per second. */
563 create_image (bp, dltime);
564 display_image (bp->buffer);
565 bp->last_screen_update = dltime;
569 bar_finish (void *progress, long dltime)
571 struct bar_progress *bp = progress;
573 if (bp->total_length > 0
574 && bp->count + bp->initial_length > bp->total_length)
575 /* See bar_update() for explanation. */
576 bp->total_length = bp->initial_length + bp->count;
578 create_image (bp, dltime);
579 display_image (bp->buffer);
581 logputs (LOG_VERBOSE, "\n\n");
587 #define APPEND_LITERAL(s) do { \
588 memcpy (p, s, sizeof (s) - 1); \
589 p += sizeof (s) - 1; \
593 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
597 create_image (struct bar_progress *bp, long dl_total_time)
599 char *p = bp->buffer;
600 long size = bp->initial_length + bp->count;
602 char *size_legible = legible (size);
603 int size_legible_len = strlen (size_legible);
605 struct bar_progress_hist *hist = &bp->hist;
607 /* The progress bar should look like this:
608 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
610 Calculate the geometry. The idea is to assign as much room as
611 possible to the progress bar. The other idea is to never let
612 things "jitter", i.e. pad elements that vary in size so that
613 their variance does not affect the placement of other elements.
614 It would be especially bad for the progress bar to be resized
617 "xx% " or "100%" - percentage - 4 chars
618 "[]" - progress bar decorations - 2 chars
619 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
620 " 1012.56K/s" - dl rate - 11 chars
621 " ETA xx:xx:xx" - ETA - 13 chars
623 "=====>..." - progress bar - the rest
625 int dlbytes_size = 1 + MAX (size_legible_len, 11);
626 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
628 if (progress_size < 5)
632 if (bp->total_length > 0)
634 int percentage = (int)(100.0 * size / bp->total_length);
636 assert (percentage <= 100);
638 if (percentage < 100)
639 sprintf (p, "%2d%% ", percentage);
645 APPEND_LITERAL (" ");
647 /* The progress bar: "[====> ]" */
648 if (progress_size && bp->total_length > 0)
650 double fraction = (double)size / bp->total_length;
651 int dlsz = (int)(fraction * progress_size);
654 assert (dlsz <= progress_size);
661 /* Draw dlsz-1 '=' chars and one arrow char. */
667 while (p - begin < progress_size)
672 else if (progress_size)
674 /* If we can't draw a real progress bar, then at least show
675 *something* to the user. */
676 int ind = bp->tick % (progress_size * 2 - 6);
679 /* Make the star move in two directions. */
680 if (ind < progress_size - 2)
683 pos = progress_size - (ind - progress_size + 5);
686 for (i = 0; i < progress_size; i++)
688 if (i == pos - 1) *p++ = '<';
689 else if (i == pos ) *p++ = '=';
690 else if (i == pos + 1) *p++ = '>';
700 sprintf (p, " %-11s", legible (size));
704 if (hist->summed_times && hist->summed_bytes)
706 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
709 dlrate = calc_rate (hist->summed_bytes, hist->summed_times, &units);
710 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
714 APPEND_LITERAL (" --.--K/s");
716 /* " ETA xx:xx:xx" */
717 if (bp->total_length > 0 && dl_total_time > 3000)
720 int eta_hrs, eta_min, eta_sec;
722 /* Don't change the value of ETA more than approximately once
723 per second; doing so would cause flashing without providing
724 any value to the user. */
725 if (dl_total_time - bp->last_eta_time < 900
726 && bp->last_eta_value != 0)
727 eta = bp->last_eta_value;
730 /* Calculate ETA using the average download speed to predict
731 the future speed. If you want to use the current speed
732 instead, replace dl_total_time with hist->summed_times
733 and bp->count with hist->summed_bytes. I found that
734 doing that results in a very jerky and ultimately
736 double time_sofar = (double)dl_total_time / 1000;
737 long bytes_remaining = bp->total_length - size;
738 eta = (long) (time_sofar * bytes_remaining / bp->count);
739 bp->last_eta_value = eta;
740 bp->last_eta_time = dl_total_time;
743 eta_hrs = eta / 3600, eta %= 3600;
744 eta_min = eta / 60, eta %= 60;
752 /* Hours not printed: pad with three spaces. */
753 APPEND_LITERAL (" ");
754 sprintf (p, " ETA %02d:%02d", eta_min, eta_sec);
759 /* Hours printed with one digit: pad with one space. */
761 sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec);
765 else if (bp->total_length > 0)
768 APPEND_LITERAL (" ");
771 assert (p - bp->buffer <= bp->width);
773 while (p < bp->buffer + bp->width)
778 /* Print the contents of the buffer as a one-line ASCII "image" so
779 that it can be overwritten next time. */
782 display_image (char *buf)
784 int old = log_set_save_context (0);
785 logputs (LOG_VERBOSE, "\r");
786 logputs (LOG_VERBOSE, buf);
787 log_set_save_context (old);
791 bar_set_params (const char *params)
796 && 0 == strcmp (params, "force"))
797 current_impl_locked = 1;
801 || !isatty (fileno (stderr))
806 && !current_impl_locked)
808 /* We're not printing to a TTY, so revert to the fallback
809 display. #### We're recursively calling
810 set_progress_implementation here, which is slightly kludgy.
811 It would be nicer if we provided that function a return value
812 indicating a failure of some sort. */
813 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
817 sw = determine_screen_width ();
818 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
824 progress_handle_sigwinch (int sig)
826 int sw = determine_screen_width ();
827 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
829 signal (SIGWINCH, progress_handle_sigwinch);