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 logputs (LOG_VERBOSE, "\n "); /* leave spacing untranslated */
194 logprintf (LOG_VERBOSE, _("[ skipping %dK ]"),
195 (int) (skipped / 1024));
198 logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
199 for (; remainder >= dot_bytes; remainder -= dot_bytes)
201 if (dp->dots % opt.dot_spacing == 0)
202 logputs (LOG_VERBOSE, " ");
203 logputs (LOG_VERBOSE, ",");
206 assert (dp->dots < opt.dots_in_line);
208 dp->accumulated = remainder;
209 dp->rows = skipped / row_bytes;
216 print_percentage (long bytes, long expected)
218 int percentage = (int)(100.0 * bytes / expected);
219 logprintf (LOG_VERBOSE, "%3d%%", percentage);
223 print_download_speed (struct dot_progress *dp, long bytes)
225 long timer_value = wtimer_elapsed (dp->timer);
226 logprintf (LOG_VERBOSE, " %s",
227 rate (bytes, timer_value - dp->last_timer_value, 1));
228 dp->last_timer_value = timer_value;
231 /* Dot-progress backend for progress_update. */
234 dot_update (void *progress, long howmuch)
236 struct dot_progress *dp = progress;
237 int dot_bytes = opt.dot_bytes;
238 long row_bytes = opt.dot_bytes * opt.dots_in_line;
242 dp->accumulated += howmuch;
243 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
246 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
248 if (dp->dots % opt.dot_spacing == 0)
249 logputs (LOG_VERBOSE, " ");
250 logputs (LOG_VERBOSE, ".");
253 if (dp->dots >= opt.dots_in_line)
258 if (dp->total_length)
259 print_percentage (dp->rows * row_bytes, dp->total_length);
261 print_download_speed (dp,
262 row_bytes - (dp->initial_length % row_bytes));
269 /* Dot-progress backend for progress_finish. */
272 dot_finish (void *progress)
274 struct dot_progress *dp = progress;
275 int dot_bytes = opt.dot_bytes;
276 long row_bytes = opt.dot_bytes * opt.dots_in_line;
281 for (i = dp->dots; i < opt.dots_in_line; i++)
283 if (i % opt.dot_spacing == 0)
284 logputs (LOG_VERBOSE, " ");
285 logputs (LOG_VERBOSE, " ");
287 if (dp->total_length)
289 print_percentage (dp->rows * row_bytes
290 + dp->dots * dot_bytes
295 print_download_speed (dp, dp->dots * dot_bytes
297 - dp->initial_length % row_bytes);
298 logputs (LOG_VERBOSE, "\n\n");
302 wtimer_delete (dp->timer);
306 /* This function interprets the progress "parameters". For example,
307 if Wget is invoked with --progress=bar:mega, it will set the
308 "dot-style" to "mega". Valid styles are default, binary, mega, and
312 dot_set_params (const char *params)
317 /* We use this to set the retrieval style. */
318 if (!strcasecmp (params, "default"))
320 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
322 opt.dot_bytes = 1024;
323 opt.dot_spacing = 10;
324 opt.dots_in_line = 50;
326 else if (!strcasecmp (params, "binary"))
328 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
330 opt.dot_bytes = 8192;
331 opt.dot_spacing = 16;
332 opt.dots_in_line = 48;
334 else if (!strcasecmp (params, "mega"))
336 /* "Mega" retrieval, for retrieving very long files; each dot is
337 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
338 opt.dot_bytes = 65536L;
340 opt.dots_in_line = 48;
342 else if (!strcasecmp (params, "giga"))
344 /* "Giga" retrieval, for retrieving very very *very* long files;
345 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
347 opt.dot_bytes = (1L << 20);
349 opt.dots_in_line = 32;
353 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
357 /* "Thermometer" (bar) progress. */
359 /* Assumed screen width if we can't find the real value. */
360 #define DEFAULT_SCREEN_WIDTH 80
362 /* Minimum screen width we'll try to work with. If this is too small,
363 create_image will overflow the buffer. */
364 #define MINIMUM_SCREEN_WIDTH 45
366 static int screen_width = DEFAULT_SCREEN_WIDTH;
368 struct bar_progress {
369 long initial_length; /* how many bytes have been downloaded
371 long total_length; /* expected total byte count when the
373 long count; /* bytes downloaded so far */
375 struct wget_timer *timer; /* timer used to measure the download
377 long last_update; /* time of the last screen update. */
379 int width; /* screen width at the time the
380 progress gauge was created. */
381 char *buffer; /* buffer where the bar "image" is
385 static void create_image PARAMS ((struct bar_progress *, long));
386 static void display_image PARAMS ((char *));
389 bar_create (long initial, long total)
391 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
393 memset (bp, 0, sizeof (*bp));
395 bp->initial_length = initial;
396 bp->total_length = total;
397 bp->timer = wtimer_new ();
398 bp->width = screen_width;
399 bp->buffer = xmalloc (bp->width + 1);
401 logputs (LOG_VERBOSE, "\n");
403 create_image (bp, 0);
404 display_image (bp->buffer);
410 bar_update (void *progress, long howmuch)
412 struct bar_progress *bp = progress;
413 int force_update = 0;
414 long dltime = wtimer_elapsed (bp->timer);
416 bp->count += howmuch;
417 if (bp->total_length > 0
418 && bp->count + bp->initial_length > bp->total_length)
419 /* We could be downloading more than total_length, e.g. when the
420 server sends an incorrect Content-Length header. In that case,
421 adjust bp->total_length to the new reality, so that the code in
422 create_image() that depends on total size being smaller or
423 equal to the expected size doesn't abort. */
424 bp->total_length = bp->count + bp->initial_length;
426 if (screen_width != bp->width)
428 bp->width = screen_width;
429 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
432 if (dltime - bp->last_update < 200 && !force_update)
433 /* Don't update more often than every half a second. */
436 bp->last_update = dltime;
438 create_image (bp, dltime);
439 display_image (bp->buffer);
443 bar_finish (void *progress)
445 struct bar_progress *bp = progress;
446 long elapsed = wtimer_elapsed (bp->timer);
449 /* If the download was faster than the granularity of the timer,
450 fake some output so that we don't get the ugly "----.--" rate
451 at the download finish. */
454 create_image (bp, elapsed);
455 display_image (bp->buffer);
457 logputs (LOG_VERBOSE, "\n\n");
460 wtimer_delete (bp->timer);
465 create_image (struct bar_progress *bp, long dltime)
467 char *p = bp->buffer;
468 long size = bp->initial_length + bp->count;
470 /* The progress bar should look like this:
471 xxx% |=======> | xx KB/s nnnnn ETA: 00:00
473 Calculate its geometry:
475 "xxx% " - percentage - 5 chars
476 "| ... |" - progress bar decorations - 2 chars
477 "1012.56 K/s " - dl rate - 12 chars
478 "nnnn " - downloaded bytes - 11 chars
479 "ETA: xx:xx:xx" - ETA - 13 chars
481 "=====>..." - progress bar content - the rest
483 int progress_len = screen_width - (5 + 2 + 12 + 11 + 13);
485 if (progress_len < 7)
489 if (bp->total_length > 0)
491 int percentage = (int)(100.0 * size / bp->total_length);
493 assert (percentage <= 100);
495 sprintf (p, "%3d%% ", percentage);
499 /* The progress bar: "|====> | " */
500 if (progress_len && bp->total_length > 0)
502 double fraction = (double)size / bp->total_length;
503 int dlsz = (int)(fraction * progress_len);
506 assert (dlsz <= progress_len);
513 /* Draw dlsz-1 '=' chars and one arrow char. */
519 while (p - begin < progress_len)
527 if (dltime && bp->count)
529 char *rt = rate (bp->count, dltime, 1);
536 strcpy (p, " --.-- K/s ");
541 sprintf (p, "%ld ", size);
544 /* "ETA: xx:xx:xx" */
545 if (bp->total_length > 0 && bp->count > 0)
547 int eta, eta_hrs, eta_min, eta_sec;
548 double tm_sofar = (double)dltime / 1000;
549 long bytes_remaining = bp->total_length - size;
551 eta = (int) (tm_sofar * bytes_remaining / bp->count);
553 eta_hrs = eta / 3600, eta %= 3600;
554 eta_min = eta / 60, eta %= 60;
557 /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
558 /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
567 /* Bogus value, for whatever reason. We must avoid overflow. */
568 sprintf (p, "--:--");
569 else if (eta_hrs > 0)
570 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
572 sprintf (p, "%02d:%02d", eta_min, eta_sec);
575 else if (bp->total_length > 0)
577 strcpy (p, "ETA: --:--");
581 assert (p - bp->buffer <= screen_width);
583 while (p < bp->buffer + screen_width)
588 /* Print the contents of the buffer as a one-line ASCII "image" so
589 that it can be overwritten next time. */
592 display_image (char *buf)
594 int len = strlen (buf);
595 char *del_buf = alloca (len + 1);
597 logputs (LOG_VERBOSE, buf);
599 memset (del_buf, '\b', len);
602 logputs (LOG_VERBOSE, del_buf);
606 bar_set_params (const char *params)
612 || !isatty (fileno (stderr))
618 && 0 == strcmp (params, "force")))
620 /* We're not printing to a TTY, so revert to the fallback
621 display. #### We're recursively calling
622 set_progress_implementation here, which is slightly kludgy.
623 It would be nicer if that function could resolve this problem
625 set_progress_implementation (NULL);
629 sw = determine_screen_width ();
630 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
635 progress_handle_sigwinch (int sig)
637 int sw = determine_screen_width ();
638 if (sw && sw >= MINIMUM_SCREEN_WIDTH)