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 struct bar_progress {
407 long initial_length; /* how many bytes have been downloaded
409 long total_length; /* expected total byte count when the
411 long count; /* bytes downloaded so far */
413 long last_update; /* time of the last screen update. */
415 int width; /* screen width we're using at the
416 time the progress gauge was
417 created. this is different from
418 the screen_width global variable in
419 that the latter can be changed by a
421 char *buffer; /* buffer where the bar "image" is
426 static void create_image PARAMS ((struct bar_progress *, long));
427 static void display_image PARAMS ((char *));
430 bar_create (long initial, long total)
432 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
434 memset (bp, 0, sizeof (*bp));
436 bp->initial_length = initial;
437 bp->total_length = total;
439 /* - 1 because we don't want to use the last screen column. */
440 bp->width = screen_width - 1;
441 /* + 1 for the terminating zero. */
442 bp->buffer = xmalloc (bp->width + 1);
444 logputs (LOG_VERBOSE, "\n");
446 create_image (bp, 0);
447 display_image (bp->buffer);
453 bar_update (void *progress, long howmuch, long dltime)
455 struct bar_progress *bp = progress;
456 int force_update = 0;
458 bp->count += howmuch;
459 if (bp->total_length > 0
460 && bp->count + bp->initial_length > bp->total_length)
461 /* We could be downloading more than total_length, e.g. when the
462 server sends an incorrect Content-Length header. In that case,
463 adjust bp->total_length to the new reality, so that the code in
464 create_image() that depends on total size being smaller or
465 equal to the expected size doesn't abort. */
466 bp->total_length = bp->count + bp->initial_length;
468 if (screen_width - 1 != bp->width)
470 bp->width = screen_width - 1;
471 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
475 if (dltime - bp->last_update < 200 && !force_update)
476 /* Don't update more often than five times per second. */
479 bp->last_update = dltime;
481 create_image (bp, dltime);
482 display_image (bp->buffer);
486 bar_finish (void *progress, long dltime)
488 struct bar_progress *bp = progress;
491 /* If the download was faster than the granularity of the timer,
492 fake some output so that we don't get the ugly "----.--" rate
493 at the download finish. */
496 create_image (bp, dltime);
497 display_image (bp->buffer);
499 logputs (LOG_VERBOSE, "\n\n");
505 #define APPEND_LITERAL(s) do { \
506 memcpy (p, s, sizeof (s) - 1); \
507 p += sizeof (s) - 1; \
511 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
515 create_image (struct bar_progress *bp, long dltime)
517 char *p = bp->buffer;
518 long size = bp->initial_length + bp->count;
520 char *size_legible = legible (size);
521 int size_legible_len = strlen (size_legible);
523 /* The progress bar should look like this:
524 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
526 Calculate the geometry. The idea is to assign as much room as
527 possible to the progress bar. The other idea is to never let
528 things "jitter", i.e. pad elements that vary in size so that
529 their variance does not affect the placement of other elements.
530 It would be especially bad for the progress bar to be resized
533 "xx% " or "100%" - percentage - 4 chars
534 "[]" - progress bar decorations - 2 chars
535 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
536 " 1012.56K/s" - dl rate - 11 chars
537 " ETA xx:xx:xx" - ETA - 13 chars
539 "=====>..." - progress bar - the rest
541 int dlbytes_size = 1 + MAX (size_legible_len, 11);
542 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
544 if (progress_size < 5)
548 if (bp->total_length > 0)
550 int percentage = (int)(100.0 * size / bp->total_length);
552 assert (percentage <= 100);
554 if (percentage < 100)
555 sprintf (p, "%2d%% ", percentage);
561 APPEND_LITERAL (" ");
563 /* The progress bar: "[====> ]" */
564 if (progress_size && bp->total_length > 0)
566 double fraction = (double)size / bp->total_length;
567 int dlsz = (int)(fraction * progress_size);
570 assert (dlsz <= progress_size);
577 /* Draw dlsz-1 '=' chars and one arrow char. */
583 while (p - begin < progress_size)
588 else if (progress_size)
590 /* If we can't draw a real progress bar, then at least show
591 *something* to the user. */
592 int ind = bp->tick % (progress_size * 2 - 6);
595 /* Make the star move in two directions. */
596 if (ind < progress_size - 2)
599 pos = progress_size - (ind - progress_size + 5);
602 for (i = 0; i < progress_size; i++)
604 if (i == pos - 1) *p++ = '<';
605 else if (i == pos ) *p++ = '=';
606 else if (i == pos + 1) *p++ = '>';
616 sprintf (p, " %-11s", legible (size));
620 if (dltime && bp->count)
622 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
624 double dlrate = calc_rate (bp->count, dltime, &units);
625 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
629 APPEND_LITERAL (" --.--K/s");
631 /* " ETA xx:xx:xx" */
632 if (bp->total_length > 0 && bp->count > 0)
634 int eta, eta_hrs, eta_min, eta_sec;
635 double tm_sofar = (double)dltime / 1000;
636 long bytes_remaining = bp->total_length - size;
638 eta = (int) (tm_sofar * bytes_remaining / bp->count);
640 eta_hrs = eta / 3600, eta %= 3600;
641 eta_min = eta / 60, eta %= 60;
644 /* Pad until the end of screen. The padding is dependent on the
646 if (eta_hrs == 0 || eta_hrs > 99)
647 /* Hours not printed: pad with three spaces (two digits and
649 APPEND_LITERAL (" ");
650 else if (eta_hrs < 10)
651 /* Hours printed with one digit: pad with one space. */
654 /* Hours printed with two digits: we're using maximum width,
658 APPEND_LITERAL (" ETA ");
661 /* Bogus value, probably due to a calculation overflow. Print
662 something safe to avoid overstepping the buffer bounds. */
663 sprintf (p, "--:--");
664 else if (eta_hrs > 0)
665 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
667 sprintf (p, "%02d:%02d", eta_min, eta_sec);
670 else if (bp->total_length > 0)
671 APPEND_LITERAL (" ETA --:--");
673 assert (p - bp->buffer <= bp->width);
675 while (p < bp->buffer + bp->width)
680 /* Print the contents of the buffer as a one-line ASCII "image" so
681 that it can be overwritten next time. */
684 display_image (char *buf)
686 int old = log_set_save_context (0);
687 logputs (LOG_VERBOSE, "\r");
688 logputs (LOG_VERBOSE, buf);
689 log_set_save_context (old);
693 bar_set_params (const char *params)
698 && 0 == strcmp (params, "force"))
699 current_impl_locked = 1;
703 || !isatty (fileno (stderr))
708 && !current_impl_locked)
710 /* We're not printing to a TTY, so revert to the fallback
711 display. #### We're recursively calling
712 set_progress_implementation here, which is slightly kludgy.
713 It would be nicer if we provided that function a return value
714 indicating a failure of some sort. */
715 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
719 sw = determine_screen_width ();
720 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
726 progress_handle_sigwinch (int sig)
728 int sw = determine_screen_width ();
729 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
731 signal (SIGWINCH, progress_handle_sigwinch);