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 */
39 struct progress_implementation {
41 void *(*create) (long, long);
42 void (*update) (void *, long, long);
43 void (*finish) (void *, long);
44 void (*set_params) (const char *);
47 /* Necessary forward declarations. */
49 static void *dot_create PARAMS ((long, long));
50 static void dot_update PARAMS ((void *, long, long));
51 static void dot_finish PARAMS ((void *, long));
52 static void dot_set_params PARAMS ((const char *));
54 static void *bar_create PARAMS ((long, long));
55 static void bar_update PARAMS ((void *, long, long));
56 static void bar_finish PARAMS ((void *, long));
57 static void bar_set_params PARAMS ((const char *));
59 static struct progress_implementation implementations[] = {
60 { "dot", dot_create, dot_update, dot_finish, dot_set_params },
61 { "bar", bar_create, bar_update, bar_finish, bar_set_params }
63 static struct progress_implementation *current_impl;
64 int current_impl_locked;
66 /* Progress implementation used by default. Can be overriden in
67 wgetrc or by the fallback one. */
69 #define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
71 /* Fallnback progress implementation should be something that works
72 under all display types. If you put something other than "dot"
73 here, remember that bar_set_params tries to switch to this if we're
74 not running on a TTY. So changing this to "bar" could cause
77 #define FALLBACK_PROGRESS_IMPLEMENTATION "dot"
79 /* Return non-zero if NAME names a valid progress bar implementation.
80 The characters after the first : will be ignored. */
83 valid_progress_implementation_p (const char *name)
86 struct progress_implementation *pi = implementations;
87 char *colon = strchr (name, ':');
88 int namelen = colon ? colon - name : strlen (name);
90 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
91 if (!strncmp (pi->name, name, namelen))
96 /* Set the progress implementation to NAME. */
99 set_progress_implementation (const char *name)
102 struct progress_implementation *pi = implementations;
106 name = DEFAULT_PROGRESS_IMPLEMENTATION;
108 colon = strchr (name, ':');
109 namelen = colon ? colon - name : strlen (name);
111 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
112 if (!strncmp (pi->name, name, namelen))
115 current_impl_locked = 0;
118 /* We call pi->set_params even if colon is NULL because we
119 want to give the implementation a chance to set up some
120 things it needs to run. */
124 pi->set_params (colon);
130 static int output_redirected;
133 progress_schedule_redirect (void)
135 output_redirected = 1;
138 /* Create a progress gauge. INITIAL is the number of bytes the
139 download starts from (zero if the download starts from scratch).
140 TOTAL is the expected total number of bytes in this download. If
141 TOTAL is zero, it means that the download size is not known in
145 progress_create (long initial, long total)
147 /* Check if the log status has changed under our feet. */
148 if (output_redirected)
150 if (!current_impl_locked)
151 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
152 output_redirected = 0;
155 return current_impl->create (initial, total);
158 /* Inform the progress gauge of newly received bytes. DLTIME is the
159 time in milliseconds since the beginning of the download. */
162 progress_update (void *progress, long howmuch, long dltime)
164 current_impl->update (progress, howmuch, dltime);
167 /* Tell the progress gauge to clean up. Calling this will free the
168 PROGRESS object, the further use of which is not allowed. */
171 progress_finish (void *progress, long dltime)
173 current_impl->finish (progress, dltime);
178 struct dot_progress {
179 long initial_length; /* how many bytes have been downloaded
181 long total_length; /* expected total byte count when the
186 int rows; /* number of rows printed so far */
187 int dots; /* number of dots printed in this row */
188 long last_timer_value;
191 /* Dot-progress backend for progress_create. */
194 dot_create (long initial, long total)
196 struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
198 memset (dp, 0, sizeof (*dp));
200 dp->initial_length = initial;
201 dp->total_length = total;
203 if (dp->initial_length)
205 int dot_bytes = opt.dot_bytes;
206 long row_bytes = opt.dot_bytes * opt.dots_in_line;
208 int remainder = (int) (dp->initial_length % row_bytes);
209 long skipped = dp->initial_length - remainder;
213 int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
214 int skipped_k_len = numdigit (skipped_k);
215 if (skipped_k_len < 5)
218 /* Align the [ skipping ... ] line with the dots. To do
219 that, insert the number of spaces equal to the number of
220 digits in the skipped amount in K. */
221 logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"),
222 2 + skipped_k_len, "", skipped_k);
225 logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
226 for (; remainder >= dot_bytes; remainder -= dot_bytes)
228 if (dp->dots % opt.dot_spacing == 0)
229 logputs (LOG_VERBOSE, " ");
230 logputs (LOG_VERBOSE, ",");
233 assert (dp->dots < opt.dots_in_line);
235 dp->accumulated = remainder;
236 dp->rows = skipped / row_bytes;
243 print_percentage (long bytes, long expected)
245 int percentage = (int)(100.0 * bytes / expected);
246 logprintf (LOG_VERBOSE, "%3d%%", percentage);
250 print_download_speed (struct dot_progress *dp, long bytes, long dltime)
252 logprintf (LOG_VERBOSE, " %s",
253 retr_rate (bytes, dltime - dp->last_timer_value, 1));
254 dp->last_timer_value = dltime;
257 /* Dot-progress backend for progress_update. */
260 dot_update (void *progress, long howmuch, long dltime)
262 struct dot_progress *dp = progress;
263 int dot_bytes = opt.dot_bytes;
264 long row_bytes = opt.dot_bytes * opt.dots_in_line;
268 dp->accumulated += howmuch;
269 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
272 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
274 if (dp->dots % opt.dot_spacing == 0)
275 logputs (LOG_VERBOSE, " ");
276 logputs (LOG_VERBOSE, ".");
279 if (dp->dots >= opt.dots_in_line)
281 long row_qty = row_bytes;
282 if (dp->rows == dp->initial_length / row_bytes)
283 row_qty -= dp->initial_length % row_bytes;
288 if (dp->total_length)
289 print_percentage (dp->rows * row_bytes, dp->total_length);
290 print_download_speed (dp, row_qty, dltime);
297 /* Dot-progress backend for progress_finish. */
300 dot_finish (void *progress, long dltime)
302 struct dot_progress *dp = progress;
303 int dot_bytes = opt.dot_bytes;
304 long row_bytes = opt.dot_bytes * opt.dots_in_line;
310 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
311 for (i = dp->dots; i < opt.dots_in_line; i++)
313 if (i % opt.dot_spacing == 0)
314 logputs (LOG_VERBOSE, " ");
315 logputs (LOG_VERBOSE, " ");
317 if (dp->total_length)
319 print_percentage (dp->rows * row_bytes
320 + dp->dots * dot_bytes
326 long row_qty = dp->dots * dot_bytes + dp->accumulated;
327 if (dp->rows == dp->initial_length / row_bytes)
328 row_qty -= dp->initial_length % row_bytes;
329 print_download_speed (dp, row_qty, dltime);
332 logputs (LOG_VERBOSE, "\n\n");
338 /* This function interprets the progress "parameters". For example,
339 if Wget is invoked with --progress=dot:mega, it will set the
340 "dot-style" to "mega". Valid styles are default, binary, mega, and
344 dot_set_params (const char *params)
346 if (!params || !*params)
347 params = opt.dot_style;
352 /* We use this to set the retrieval style. */
353 if (!strcasecmp (params, "default"))
355 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
357 opt.dot_bytes = 1024;
358 opt.dot_spacing = 10;
359 opt.dots_in_line = 50;
361 else if (!strcasecmp (params, "binary"))
363 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
365 opt.dot_bytes = 8192;
366 opt.dot_spacing = 16;
367 opt.dots_in_line = 48;
369 else if (!strcasecmp (params, "mega"))
371 /* "Mega" retrieval, for retrieving very long files; each dot is
372 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
373 opt.dot_bytes = 65536L;
375 opt.dots_in_line = 48;
377 else if (!strcasecmp (params, "giga"))
379 /* "Giga" retrieval, for retrieving very very *very* long files;
380 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
382 opt.dot_bytes = (1L << 20);
384 opt.dots_in_line = 32;
388 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
392 /* "Thermometer" (bar) progress. */
394 /* Assumed screen width if we can't find the real value. */
395 #define DEFAULT_SCREEN_WIDTH 80
397 /* Minimum screen width we'll try to work with. If this is too small,
398 create_image will overflow the buffer. */
399 #define MINIMUM_SCREEN_WIDTH 45
401 static int screen_width = DEFAULT_SCREEN_WIDTH;
403 struct bar_progress {
404 long initial_length; /* how many bytes have been downloaded
406 long total_length; /* expected total byte count when the
408 long count; /* bytes downloaded so far */
410 long last_update; /* time of the last screen update. */
412 int width; /* screen width at the time the
413 progress gauge was created. */
414 char *buffer; /* buffer where the bar "image" is
420 static void create_image PARAMS ((struct bar_progress *, long));
421 static void display_image PARAMS ((char *));
424 bar_create (long initial, long total)
426 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
428 memset (bp, 0, sizeof (*bp));
430 bp->initial_length = initial;
431 bp->total_length = total;
432 bp->width = screen_width;
433 bp->buffer = xmalloc (bp->width + 1);
435 logputs (LOG_VERBOSE, "\n");
437 create_image (bp, 0);
438 display_image (bp->buffer);
444 bar_update (void *progress, long howmuch, long dltime)
446 struct bar_progress *bp = progress;
447 int force_update = 0;
449 bp->count += howmuch;
450 if (bp->total_length > 0
451 && bp->count + bp->initial_length > bp->total_length)
452 /* We could be downloading more than total_length, e.g. when the
453 server sends an incorrect Content-Length header. In that case,
454 adjust bp->total_length to the new reality, so that the code in
455 create_image() that depends on total size being smaller or
456 equal to the expected size doesn't abort. */
457 bp->total_length = bp->count + bp->initial_length;
459 if (screen_width != bp->width)
461 bp->width = screen_width;
462 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
465 if (dltime - bp->last_update < 200 && !force_update)
466 /* Don't update more often than every half a second. */
469 bp->last_update = dltime;
471 create_image (bp, dltime);
472 display_image (bp->buffer);
476 bar_finish (void *progress, long dltime)
478 struct bar_progress *bp = progress;
481 /* If the download was faster than the granularity of the timer,
482 fake some output so that we don't get the ugly "----.--" rate
483 at the download finish. */
486 create_image (bp, dltime);
487 display_image (bp->buffer);
489 logputs (LOG_VERBOSE, "\n\n");
496 create_image (struct bar_progress *bp, long dltime)
498 char *p = bp->buffer;
499 long size = bp->initial_length + bp->count;
501 /* The progress bar should look like this:
502 xx% [=======> ] nn.nnn rrK/s ETA 00:00
504 Calculate its geometry:
506 "xx% " or "100%" - percentage - 4 chars exactly
507 "[]" - progress bar decorations - 2 chars exactly
508 " n,nnn,nnn,nnn" - downloaded bytes - 14 or less chars
509 " 1012.56K/s" - dl rate - 11 chars exactly
510 " ETA xx:xx:xx" - ETA - 13 or less chars
512 "=====>..." - progress bar content - the rest
514 int progress_size = screen_width - (4 + 2 + 14 + 11 + 13);
516 if (progress_size < 5)
520 if (bp->total_length > 0)
522 int percentage = (int)(100.0 * size / bp->total_length);
524 assert (percentage <= 100);
526 if (percentage < 100)
527 sprintf (p, "%2d%% ", percentage);
540 /* The progress bar: "[====> ]" */
541 if (progress_size && bp->total_length > 0)
543 double fraction = (double)size / bp->total_length;
544 int dlsz = (int)(fraction * progress_size);
547 assert (dlsz <= progress_size);
554 /* Draw dlsz-1 '=' chars and one arrow char. */
560 while (p - begin < progress_size)
565 else if (progress_size)
567 /* If we can't draw a real progress bar, then at least show
568 *something* to the user. */
569 int ind = bp->tick % (progress_size * 2 - 6);
572 /* Make the star move in two directions. */
573 if (ind < progress_size - 2)
576 pos = progress_size - (ind - progress_size + 5);
579 for (i = 0; i < progress_size; i++)
581 if (i == pos - 1) *p++ = '<';
582 else if (i == pos ) *p++ = '=';
583 else if (i == pos + 1) *p++ = '>';
593 /* If there are 7 or less digits (9 because of "legible" comas),
594 print the number in constant space. This will prevent the rest
595 of the line jerking at the beginning of download, but without
596 assigning maximum width in all cases. */
597 sprintf (p, " %9s", legible (size));
601 if (dltime && bp->count)
603 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
605 double dlrate = calc_rate (bp->count, dltime, &units);
606 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
611 strcpy (p, " --.--K/s");
615 /* " ETA xx:xx:xx" */
616 if (bp->total_length > 0 && bp->count > 0)
618 int eta, eta_hrs, eta_min, eta_sec;
619 double tm_sofar = (double)dltime / 1000;
620 long bytes_remaining = bp->total_length - size;
622 eta = (int) (tm_sofar * bytes_remaining / bp->count);
624 eta_hrs = eta / 3600, eta %= 3600;
625 eta_min = eta / 60, eta %= 60;
628 /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
629 /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
638 /* Bogus value, for whatever reason. We must avoid overflow. */
639 sprintf (p, "--:--");
640 else if (eta_hrs > 0)
641 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
643 sprintf (p, "%02d:%02d", eta_min, eta_sec);
646 else if (bp->total_length > 0)
648 strcpy (p, " ETA --:--");
652 assert (p - bp->buffer <= screen_width);
654 while (p < bp->buffer + screen_width)
659 /* Print the contents of the buffer as a one-line ASCII "image" so
660 that it can be overwritten next time. */
663 display_image (char *buf)
665 logputs (LOG_VERBOSE, "\r");
666 logputs (LOG_VERBOSE, buf);
670 bar_set_params (const char *params)
675 && 0 == strcmp (params, "force"))
676 current_impl_locked = 1;
680 || !isatty (fileno (stderr))
685 && !current_impl_locked)
687 /* We're not printing to a TTY, so revert to the fallback
688 display. #### We're recursively calling
689 set_progress_implementation here, which is slightly kludgy.
690 It would be nicer if we provided that function a return value
691 indicating a failure of some sort. */
692 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
696 sw = determine_screen_width ();
697 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
702 progress_handle_sigwinch (int sig)
704 int sw = determine_screen_width ();
705 if (sw && sw >= MINIMUM_SCREEN_WIDTH)