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);
43 void (*finish) (void *);
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));
51 static void dot_finish PARAMS ((void *));
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));
56 static void bar_finish PARAMS ((void *));
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;
65 /* Default progress implementation should be something that works
66 under all display types. If you put something other than "dot"
67 here, remember that bar_set_params tries to switch to this if we're
68 not running on a TTY. So changing this to "bar" could cause
71 #define DEFAULT_PROGRESS_IMPLEMENTATION "dot"
73 /* Return non-zero if NAME names a valid progress bar implementation.
74 The characters after the first : will be ignored. */
77 valid_progress_implementation_p (const char *name)
80 struct progress_implementation *pi = implementations;
81 char *colon = strchr (name, ':');
82 int namelen = colon ? colon - name : strlen (name);
84 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
85 if (!strncmp (pi->name, name, namelen))
90 /* Set the progress implementation to NAME. */
93 set_progress_implementation (const char *name)
96 struct progress_implementation *pi = implementations;
100 name = DEFAULT_PROGRESS_IMPLEMENTATION;
102 colon = strchr (name, ':');
103 namelen = colon ? colon - name : strlen (name);
105 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
106 if (!strncmp (pi->name, name, namelen))
111 /* We call pi->set_params even if colon is NULL because we
112 want to give the implementation a chance to set up some
113 things it needs to run. */
117 pi->set_params (colon);
123 /* Create a progress gauge. INITIAL is the number of bytes the
124 download starts from (zero if the download starts from scratch).
125 TOTAL is the expected total number of bytes in this download. If
126 TOTAL is zero, it means that the download size is not known in
130 progress_create (long initial, long total)
132 return current_impl->create (initial, total);
135 /* Inform the progress gauge of newly received bytes. */
138 progress_update (void *progress, long howmuch)
140 current_impl->update (progress, howmuch);
143 /* Tell the progress gauge to clean up. Calling this will free the
144 PROGRESS object, the further use of which is not allowed. */
147 progress_finish (void *progress)
149 current_impl->finish (progress);
154 struct dot_progress {
155 long initial_length; /* how many bytes have been downloaded
157 long total_length; /* expected total byte count when the
162 int rows; /* number of rows printed so far */
163 int dots; /* number of dots printed in this row */
165 struct wget_timer *timer; /* timer used to measure per-row
167 long last_timer_value;
170 /* Dot-progress backend for progress_create. */
173 dot_create (long initial, long total)
175 struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
177 memset (dp, 0, sizeof (*dp));
179 dp->initial_length = initial;
180 dp->total_length = total;
181 dp->timer = wtimer_new ();
183 if (dp->initial_length)
185 int dot_bytes = opt.dot_bytes;
186 long row_bytes = opt.dot_bytes * opt.dots_in_line;
188 int remainder = (int) (dp->initial_length % row_bytes);
189 long skipped = dp->initial_length - remainder;
193 int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
194 int skipped_k_len = numdigit (skipped_k);
195 if (skipped_k_len < 5)
198 /* Align the [ skipping ... ] line with the dots. To do
199 that, insert the number of spaces equal to the number of
200 digits in the skipped amount in K. */
201 logprintf (LOG_VERBOSE, "\n%*s%s",
202 2 + skipped_k_len, "", _("[ skipping %dK ]"));
205 logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
206 for (; remainder >= dot_bytes; remainder -= dot_bytes)
208 if (dp->dots % opt.dot_spacing == 0)
209 logputs (LOG_VERBOSE, " ");
210 logputs (LOG_VERBOSE, ",");
213 assert (dp->dots < opt.dots_in_line);
215 dp->accumulated = remainder;
216 dp->rows = skipped / row_bytes;
223 print_percentage (long bytes, long expected)
225 int percentage = (int)(100.0 * bytes / expected);
226 logprintf (LOG_VERBOSE, "%3d%%", percentage);
230 print_download_speed (struct dot_progress *dp, long bytes)
232 long timer_value = wtimer_elapsed (dp->timer);
233 logprintf (LOG_VERBOSE, " %s",
234 retr_rate (bytes, timer_value - dp->last_timer_value, 1));
235 dp->last_timer_value = timer_value;
238 /* Dot-progress backend for progress_update. */
241 dot_update (void *progress, long howmuch)
243 struct dot_progress *dp = progress;
244 int dot_bytes = opt.dot_bytes;
245 long row_bytes = opt.dot_bytes * opt.dots_in_line;
249 dp->accumulated += howmuch;
250 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
253 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
255 if (dp->dots % opt.dot_spacing == 0)
256 logputs (LOG_VERBOSE, " ");
257 logputs (LOG_VERBOSE, ".");
260 if (dp->dots >= opt.dots_in_line)
265 if (dp->total_length)
266 print_percentage (dp->rows * row_bytes, dp->total_length);
268 print_download_speed (dp,
269 row_bytes - (dp->initial_length % row_bytes));
276 /* Dot-progress backend for progress_finish. */
279 dot_finish (void *progress)
281 struct dot_progress *dp = progress;
282 int dot_bytes = opt.dot_bytes;
283 long row_bytes = opt.dot_bytes * opt.dots_in_line;
288 for (i = dp->dots; i < opt.dots_in_line; i++)
290 if (i % opt.dot_spacing == 0)
291 logputs (LOG_VERBOSE, " ");
292 logputs (LOG_VERBOSE, " ");
294 if (dp->total_length)
296 print_percentage (dp->rows * row_bytes
297 + dp->dots * dot_bytes
302 print_download_speed (dp, dp->dots * dot_bytes
304 - dp->initial_length % row_bytes);
305 logputs (LOG_VERBOSE, "\n\n");
309 wtimer_delete (dp->timer);
313 /* This function interprets the progress "parameters". For example,
314 if Wget is invoked with --progress=bar:mega, it will set the
315 "dot-style" to "mega". Valid styles are default, binary, mega, and
319 dot_set_params (const char *params)
324 /* We use this to set the retrieval style. */
325 if (!strcasecmp (params, "default"))
327 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
329 opt.dot_bytes = 1024;
330 opt.dot_spacing = 10;
331 opt.dots_in_line = 50;
333 else if (!strcasecmp (params, "binary"))
335 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
337 opt.dot_bytes = 8192;
338 opt.dot_spacing = 16;
339 opt.dots_in_line = 48;
341 else if (!strcasecmp (params, "mega"))
343 /* "Mega" retrieval, for retrieving very long files; each dot is
344 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
345 opt.dot_bytes = 65536L;
347 opt.dots_in_line = 48;
349 else if (!strcasecmp (params, "giga"))
351 /* "Giga" retrieval, for retrieving very very *very* long files;
352 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
354 opt.dot_bytes = (1L << 20);
356 opt.dots_in_line = 32;
360 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
364 /* "Thermometer" (bar) progress. */
366 /* Assumed screen width if we can't find the real value. */
367 #define DEFAULT_SCREEN_WIDTH 80
369 /* Minimum screen width we'll try to work with. If this is too small,
370 create_image will overflow the buffer. */
371 #define MINIMUM_SCREEN_WIDTH 45
373 static int screen_width = DEFAULT_SCREEN_WIDTH;
375 struct bar_progress {
376 long initial_length; /* how many bytes have been downloaded
378 long total_length; /* expected total byte count when the
380 long count; /* bytes downloaded so far */
382 struct wget_timer *timer; /* timer used to measure the download
384 long last_update; /* time of the last screen update. */
386 int width; /* screen width at the time the
387 progress gauge was created. */
388 char *buffer; /* buffer where the bar "image" is
394 static void create_image PARAMS ((struct bar_progress *, long));
395 static void display_image PARAMS ((char *));
398 bar_create (long initial, long total)
400 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
402 memset (bp, 0, sizeof (*bp));
404 bp->initial_length = initial;
405 bp->total_length = total;
406 bp->timer = wtimer_new ();
407 bp->width = screen_width;
408 bp->buffer = xmalloc (bp->width + 1);
410 logputs (LOG_VERBOSE, "\n");
412 create_image (bp, 0);
413 display_image (bp->buffer);
419 bar_update (void *progress, long howmuch)
421 struct bar_progress *bp = progress;
422 int force_update = 0;
423 long dltime = wtimer_elapsed (bp->timer);
425 bp->count += howmuch;
426 if (bp->total_length > 0
427 && bp->count + bp->initial_length > bp->total_length)
428 /* We could be downloading more than total_length, e.g. when the
429 server sends an incorrect Content-Length header. In that case,
430 adjust bp->total_length to the new reality, so that the code in
431 create_image() that depends on total size being smaller or
432 equal to the expected size doesn't abort. */
433 bp->total_length = bp->count + bp->initial_length;
435 if (screen_width != bp->width)
437 bp->width = screen_width;
438 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
441 if (dltime - bp->last_update < 200 && !force_update)
442 /* Don't update more often than every half a second. */
445 bp->last_update = dltime;
447 create_image (bp, dltime);
448 display_image (bp->buffer);
452 bar_finish (void *progress)
454 struct bar_progress *bp = progress;
455 long elapsed = wtimer_elapsed (bp->timer);
458 /* If the download was faster than the granularity of the timer,
459 fake some output so that we don't get the ugly "----.--" rate
460 at the download finish. */
463 create_image (bp, elapsed);
464 display_image (bp->buffer);
466 logputs (LOG_VERBOSE, "\n\n");
469 wtimer_delete (bp->timer);
474 create_image (struct bar_progress *bp, long dltime)
476 char *p = bp->buffer;
477 long size = bp->initial_length + bp->count;
479 /* The progress bar should look like this:
480 xx% [=======> ] nn.nnn rrK/s ETA 00:00
482 Calculate its geometry:
484 "xx% " or "100%" - percentage - 4 chars exactly
485 "[]" - progress bar decorations - 2 chars exactly
486 " n,nnn,nnn,nnn" - downloaded bytes - 14 or less chars
487 " 1012.56K/s" - dl rate - 11 chars exactly
488 " ETA xx:xx:xx" - ETA - 13 or less chars
490 "=====>..." - progress bar content - the rest
492 int progress_size = screen_width - (4 + 2 + 14 + 11 + 13);
494 if (progress_size < 5)
498 if (bp->total_length > 0)
500 int percentage = (int)(100.0 * size / bp->total_length);
502 assert (percentage <= 100);
504 if (percentage < 100)
505 sprintf (p, "%2d%% ", percentage);
518 /* The progress bar: "[====> ]" */
519 if (progress_size && bp->total_length > 0)
521 double fraction = (double)size / bp->total_length;
522 int dlsz = (int)(fraction * progress_size);
525 assert (dlsz <= progress_size);
532 /* Draw dlsz-1 '=' chars and one arrow char. */
538 while (p - begin < progress_size)
543 else if (progress_size)
545 /* If we can't draw a real progress bar, then at least show
546 *something* to the user. */
547 int ind = bp->tick % (progress_size * 2 - 6);
550 /* Make the star move in two directions. */
551 if (ind < progress_size - 2)
554 pos = progress_size - (ind - progress_size + 5);
557 for (i = 0; i < progress_size; i++)
559 if (i == pos - 1) *p++ = '<';
560 else if (i == pos ) *p++ = '=';
561 else if (i == pos + 1) *p++ = '>';
571 /* If there are 7 or less digits (9 because of "legible" comas),
572 print the number in constant space. This will prevent the rest
573 of the line jerking at the beginning of download, but without
574 assigning maximum width in all cases. */
575 sprintf (p, " %9s", legible (size));
579 if (dltime && bp->count)
581 static char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
583 double dlrate = calc_rate (bp->count, dltime, &units);
584 sprintf (p, " %7.2f%s", dlrate, short_units[units]);
589 strcpy (p, " --.--K/s");
593 /* " ETA xx:xx:xx" */
594 if (bp->total_length > 0 && bp->count > 0)
596 int eta, eta_hrs, eta_min, eta_sec;
597 double tm_sofar = (double)dltime / 1000;
598 long bytes_remaining = bp->total_length - size;
600 eta = (int) (tm_sofar * bytes_remaining / bp->count);
602 eta_hrs = eta / 3600, eta %= 3600;
603 eta_min = eta / 60, eta %= 60;
606 /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
607 /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
616 /* Bogus value, for whatever reason. We must avoid overflow. */
617 sprintf (p, "--:--");
618 else if (eta_hrs > 0)
619 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
621 sprintf (p, "%02d:%02d", eta_min, eta_sec);
624 else if (bp->total_length > 0)
626 strcpy (p, " ETA --:--");
630 assert (p - bp->buffer <= screen_width);
632 while (p < bp->buffer + screen_width)
637 /* Print the contents of the buffer as a one-line ASCII "image" so
638 that it can be overwritten next time. */
641 display_image (char *buf)
643 char *del_buf = alloca (screen_width + 1);
644 memset (del_buf, '\b', screen_width);
645 del_buf[screen_width] = '\0';
646 logputs (LOG_VERBOSE, del_buf);
647 logputs (LOG_VERBOSE, buf);
651 bar_set_params (const char *params)
657 || !isatty (fileno (stderr))
663 && 0 == strcmp (params, "force")))
665 /* We're not printing to a TTY, so revert to the fallback
666 display. #### We're recursively calling
667 set_progress_implementation here, which is slightly kludgy.
668 It would be nicer if that function could resolve this problem
670 set_progress_implementation (NULL);
674 sw = determine_screen_width ();
675 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
680 progress_handle_sigwinch (int sig)
682 int sw = determine_screen_width ();
683 if (sw && sw >= MINIMUM_SCREEN_WIDTH)