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 */
36 struct progress_implementation {
38 void *(*create) (long, long);
39 void (*update) (void *, long);
40 void (*finish) (void *);
41 void (*set_params) (const char *);
44 /* Necessary forward declarations. */
46 static void *dot_create PARAMS ((long, long));
47 static void dot_update PARAMS ((void *, long));
48 static void dot_finish PARAMS ((void *));
49 static void dot_set_params PARAMS ((const char *));
51 static void *bar_create PARAMS ((long, long));
52 static void bar_update PARAMS ((void *, long));
53 static void bar_finish PARAMS ((void *));
54 static void bar_set_params PARAMS ((const char *));
56 static struct progress_implementation implementations[] = {
57 { "dot", dot_create, dot_update, dot_finish, dot_set_params },
58 { "bar", bar_create, bar_update, bar_finish, bar_set_params }
60 static struct progress_implementation *current_impl;
62 /* Default progress implementation should be something that works
63 under all display types. If you put something other than "dot"
64 here, remember that bar_set_params tries to switch to this if we're
65 not running on a TTY. So changing this to "bar" could cause
68 #define DEFAULT_PROGRESS_IMPLEMENTATION "dot"
70 /* Return non-zero if NAME names a valid progress bar implementation.
71 The characters after the first : will be ignored. */
74 valid_progress_implementation_p (const char *name)
77 struct progress_implementation *pi = implementations;
78 char *colon = strchr (name, ':');
79 int namelen = colon ? colon - name : strlen (name);
81 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
82 if (!strncmp (pi->name, name, namelen))
87 /* Set the progress implementation to NAME. */
90 set_progress_implementation (const char *name)
93 struct progress_implementation *pi = implementations;
97 name = DEFAULT_PROGRESS_IMPLEMENTATION;
99 colon = strchr (name, ':');
100 namelen = colon ? colon - name : strlen (name);
102 for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
103 if (!strncmp (pi->name, name, namelen))
108 /* We call pi->set_params even if colon is NULL because we
109 want to give the implementation a chance to set up some
110 things it needs to run. */
114 pi->set_params (colon);
120 /* Create a progress gauge. INITIAL is the number of bytes the
121 download starts from (zero if the download starts from scratch).
122 TOTAL is the expected total number of bytes in this download. If
123 TOTAL is zero, it means that the download size is not known in
127 progress_create (long initial, long total)
129 return current_impl->create (initial, total);
132 /* Inform the progress gauge of newly received bytes. */
135 progress_update (void *progress, long howmuch)
137 current_impl->update (progress, howmuch);
140 /* Tell the progress gauge to clean up. Calling this will free the
141 PROGRESS object, the further use of which is not allowed. */
144 progress_finish (void *progress)
146 current_impl->finish (progress);
151 struct dot_progress {
152 long initial_length; /* how many bytes have been downloaded
154 long total_length; /* expected total byte count when the
159 int rows; /* number of rows printed so far */
160 int dots; /* number of dots printed in this row */
162 struct wget_timer *timer; /* timer used to measure per-row
164 long last_timer_value;
167 /* Dot-progress backend for progress_create. */
170 dot_create (long initial, long total)
172 struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
174 memset (dp, 0, sizeof (*dp));
176 dp->initial_length = initial;
177 dp->total_length = total;
178 dp->timer = wtimer_new ();
180 if (dp->initial_length)
182 int dot_bytes = opt.dot_bytes;
183 long row_bytes = opt.dot_bytes * opt.dots_in_line;
185 int remainder = (int) (dp->initial_length % row_bytes);
186 long skipped = dp->initial_length - remainder;
190 logputs (LOG_VERBOSE, "\n "); /* leave spacing untranslated */
191 logprintf (LOG_VERBOSE, _("[ skipping %dK ]"),
192 (int) (skipped / 1024));
195 logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
196 for (; remainder >= dot_bytes; remainder -= dot_bytes)
198 if (dp->dots % opt.dot_spacing == 0)
199 logputs (LOG_VERBOSE, " ");
200 logputs (LOG_VERBOSE, ",");
203 assert (dp->dots < opt.dots_in_line);
205 dp->accumulated = remainder;
206 dp->rows = skipped / row_bytes;
213 print_percentage (long bytes, long expected)
215 int percentage = (int)(100.0 * bytes / expected);
216 logprintf (LOG_VERBOSE, "%3d%%", percentage);
220 print_download_speed (struct dot_progress *dp, long bytes)
222 long timer_value = wtimer_elapsed (dp->timer);
223 logprintf (LOG_VERBOSE, " %s",
224 rate (bytes, timer_value - dp->last_timer_value, 1));
225 dp->last_timer_value = timer_value;
228 /* Dot-progress backend for progress_update. */
231 dot_update (void *progress, long howmuch)
233 struct dot_progress *dp = progress;
234 int dot_bytes = opt.dot_bytes;
235 long row_bytes = opt.dot_bytes * opt.dots_in_line;
239 dp->accumulated += howmuch;
240 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
243 logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
245 if (dp->dots % opt.dot_spacing == 0)
246 logputs (LOG_VERBOSE, " ");
247 logputs (LOG_VERBOSE, ".");
250 if (dp->dots >= opt.dots_in_line)
255 if (dp->total_length)
256 print_percentage (dp->rows * row_bytes, dp->total_length);
258 print_download_speed (dp,
259 row_bytes - (dp->initial_length % row_bytes));
266 /* Dot-progress backend for progress_finish. */
269 dot_finish (void *progress)
271 struct dot_progress *dp = progress;
272 int dot_bytes = opt.dot_bytes;
273 long row_bytes = opt.dot_bytes * opt.dots_in_line;
278 for (i = dp->dots; i < opt.dots_in_line; i++)
280 if (i % opt.dot_spacing == 0)
281 logputs (LOG_VERBOSE, " ");
282 logputs (LOG_VERBOSE, " ");
284 if (dp->total_length)
286 print_percentage (dp->rows * row_bytes
287 + dp->dots * dot_bytes
292 print_download_speed (dp, dp->dots * dot_bytes
294 - dp->initial_length % row_bytes);
295 logputs (LOG_VERBOSE, "\n\n");
299 wtimer_delete (dp->timer);
303 /* This function interprets the progress "parameters". For example,
304 if Wget is invoked with --progress=bar:mega, it will set the
305 "dot-style" to "mega". Valid styles are default, binary, mega, and
309 dot_set_params (const char *params)
314 /* We use this to set the retrieval style. */
315 if (!strcasecmp (params, "default"))
317 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
319 opt.dot_bytes = 1024;
320 opt.dot_spacing = 10;
321 opt.dots_in_line = 50;
323 else if (!strcasecmp (params, "binary"))
325 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
327 opt.dot_bytes = 8192;
328 opt.dot_spacing = 16;
329 opt.dots_in_line = 48;
331 else if (!strcasecmp (params, "mega"))
333 /* "Mega" retrieval, for retrieving very long files; each dot is
334 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
335 opt.dot_bytes = 65536L;
337 opt.dots_in_line = 48;
339 else if (!strcasecmp (params, "giga"))
341 /* "Giga" retrieval, for retrieving very very *very* long files;
342 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
344 opt.dot_bytes = (1L << 20);
346 opt.dots_in_line = 32;
350 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
354 /* "Thermometer" (bar) progress. */
356 /* Assumed screen width if we can't find the real value. */
357 #define DEFAULT_SCREEN_WIDTH 80
359 /* Minimum screen width we'll try to work with. If this is too small,
360 create_image will overflow the buffer. */
361 #define MINIMUM_SCREEN_WIDTH 45
363 static int screen_width = DEFAULT_SCREEN_WIDTH;
365 struct bar_progress {
366 long initial_length; /* how many bytes have been downloaded
368 long total_length; /* expected total byte count when the
370 long count; /* bytes downloaded so far */
372 struct wget_timer *timer; /* timer used to measure the download
374 long last_update; /* time of the last screen update. */
376 int width; /* screen width at the time the
377 progress gauge was created. */
378 char *buffer; /* buffer where the bar "image" is
382 static void create_image PARAMS ((struct bar_progress *, long));
383 static void display_image PARAMS ((char *));
386 bar_create (long initial, long total)
388 struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
390 memset (bp, 0, sizeof (*bp));
392 bp->initial_length = initial;
393 bp->total_length = total;
394 bp->timer = wtimer_new ();
395 bp->width = screen_width;
396 bp->buffer = xmalloc (bp->width + 1);
398 logputs (LOG_VERBOSE, "\n");
400 create_image (bp, 0);
401 display_image (bp->buffer);
407 bar_update (void *progress, long howmuch)
409 struct bar_progress *bp = progress;
410 int force_update = 0;
411 long dltime = wtimer_elapsed (bp->timer);
413 bp->count += howmuch;
414 if (bp->total_length > 0
415 && bp->count + bp->initial_length > bp->total_length)
416 /* We could be downloading more than total_length, e.g. when the
417 server sends an incorrect Content-Length header. In that case,
418 adjust bp->total_length to the new reality, so that the code in
419 create_image() that depends on total size being smaller or
420 equal to the expected size doesn't abort. */
421 bp->total_length = bp->count + bp->initial_length;
423 if (screen_width != bp->width)
425 bp->width = screen_width;
426 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
429 if (dltime - bp->last_update < 200 && !force_update)
430 /* Don't update more often than every half a second. */
433 bp->last_update = dltime;
435 create_image (bp, dltime);
436 display_image (bp->buffer);
440 bar_finish (void *progress)
442 struct bar_progress *bp = progress;
443 long elapsed = wtimer_elapsed (bp->timer);
446 /* If the download was faster than the granularity of the timer,
447 fake some output so that we don't get the ugly "----.--" rate
448 at the download finish. */
451 create_image (bp, elapsed);
452 display_image (bp->buffer);
454 logputs (LOG_VERBOSE, "\n\n");
457 wtimer_delete (bp->timer);
462 create_image (struct bar_progress *bp, long dltime)
464 char *p = bp->buffer;
465 long size = bp->initial_length + bp->count;
467 /* The progress bar should look like this:
468 xxx% |=======> | xx KB/s nnnnn ETA: 00:00
470 Calculate its geometry:
472 "xxx% " - percentage - 5 chars
473 "| ... | " - progress bar decorations - 3 chars
474 "1012.56 K/s " - dl rate - 12 chars
475 "nnnn " - downloaded bytes - 11 chars
476 "ETA: xx:xx:xx" - ETA - 13 chars
478 "=====>..." - progress bar content - the rest
480 int progress_len = screen_width - (5 + 3 + 12 + 11 + 13);
482 if (progress_len < 7)
486 if (bp->total_length > 0)
488 int percentage = (int)(100.0 * size / bp->total_length);
490 assert (percentage <= 100);
492 sprintf (p, "%3d%% ", percentage);
496 /* The progress bar: "|====> | " */
497 if (progress_len && bp->total_length > 0)
499 double fraction = (double)size / bp->total_length;
500 int dlsz = (int)(fraction * progress_len);
503 assert (dlsz <= progress_len);
510 /* Draw dlsz-1 '=' chars and one arrow char. */
516 while (p - begin < progress_len)
524 if (dltime && bp->count)
526 char *rt = rate (bp->count, dltime, 1);
533 strcpy (p, "----.-- K/s ");
538 sprintf (p, "%ld ", size);
541 /* "ETA: xx:xx:xx" */
542 if (bp->total_length > 0 && bp->count > 0)
544 int eta, eta_hrs, eta_min, eta_sec;
545 double tm_sofar = (double)dltime / 1000;
546 long bytes_remaining = bp->total_length - size;
548 eta = (int) (tm_sofar * bytes_remaining / bp->count);
550 eta_hrs = eta / 3600, eta %= 3600;
551 eta_min = eta / 60, eta %= 60;
554 /*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
555 /*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
564 /* Bogus value, for whatever reason. We must avoid overflow. */
565 sprintf (p, "--:--");
566 else if (eta_hrs > 0)
567 sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
569 sprintf (p, "%02d:%02d", eta_min, eta_sec);
572 else if (bp->total_length > 0)
574 strcpy (p, "ETA: --:--");
578 assert (p - bp->buffer <= screen_width);
580 while (p < bp->buffer + screen_width)
585 /* Print the contents of the buffer as a one-line ASCII "image" so
586 that it can be overwritten next time. */
589 display_image (char *buf)
591 int len = strlen (buf);
592 char *del_buf = alloca (len + 1);
594 logputs (LOG_VERBOSE, buf);
596 memset (del_buf, '\b', len);
599 logputs (LOG_VERBOSE, del_buf);
603 bar_set_params (const char *params)
609 || !isatty (fileno (stderr))
615 && 0 == strcmp (params, "force")))
617 /* We're not printing to a TTY, so revert to the fallback
618 display. #### We're recursively calling
619 set_progress_implementation here, which is slightly kludgy.
620 It would be nicer if that function could resolve this problem
622 set_progress_implementation (NULL);
626 sw = determine_screen_width ();
627 if (sw && sw >= MINIMUM_SCREEN_WIDTH)
632 progress_handle_sigwinch (int sig)
634 int sw = determine_screen_width ();
635 if (sw && sw >= MINIMUM_SCREEN_WIDTH)