source: trunk/essentials/net-misc/wget/src/progress.c

Last change on this file was 3440, checked in by bird, 18 years ago

wget 1.10.2

File size: 27.7 KB
Line 
1/* Download progress.
2 Copyright (C) 2001, 2002 Free Software Foundation, Inc.
3
4This file is part of GNU Wget.
5
6GNU Wget is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2 of the License, or
9(at your option) any later version.
10
11GNU Wget is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with Wget; if not, write to the Free Software
18Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20In addition, as a special exception, the Free Software Foundation
21gives permission to link the code of its release of Wget with the
22OpenSSL project's "OpenSSL" library (or with modified versions of it
23that use the same license as the "OpenSSL" library), and distribute
24the linked executables. You must obey the GNU General Public License
25in all respects for all of the code used other than "OpenSSL". If you
26modify this file, you may extend this exception to your version of the
27file, but you are not obligated to do so. If you do not wish to do
28so, delete this exception statement from your version. */
29
30#include <config.h>
31
32#include <stdio.h>
33#include <stdlib.h>
34#ifdef HAVE_STRING_H
35# include <string.h>
36#else
37# include <strings.h>
38#endif /* HAVE_STRING_H */
39#include <assert.h>
40#ifdef HAVE_UNISTD_H
41# include <unistd.h>
42#endif
43#ifdef HAVE_SIGNAL_H
44# include <signal.h>
45#endif
46
47#include "wget.h"
48#include "progress.h"
49#include "utils.h"
50#include "retr.h"
51
52struct progress_implementation {
53 const char *name;
54 int interactive;
55 void *(*create) PARAMS ((wgint, wgint));
56 void (*update) PARAMS ((void *, wgint, double));
57 void (*finish) PARAMS ((void *, double));
58 void (*set_params) PARAMS ((const char *));
59};
60
61/* Necessary forward declarations. */
62
63static void *dot_create PARAMS ((wgint, wgint));
64static void dot_update PARAMS ((void *, wgint, double));
65static void dot_finish PARAMS ((void *, double));
66static void dot_set_params PARAMS ((const char *));
67
68static void *bar_create PARAMS ((wgint, wgint));
69static void bar_update PARAMS ((void *, wgint, double));
70static void bar_finish PARAMS ((void *, double));
71static void bar_set_params PARAMS ((const char *));
72
73static struct progress_implementation implementations[] = {
74 { "dot", 0, dot_create, dot_update, dot_finish, dot_set_params },
75 { "bar", 1, bar_create, bar_update, bar_finish, bar_set_params }
76};
77static struct progress_implementation *current_impl;
78static int current_impl_locked;
79
80/* Progress implementation used by default. Can be overriden in
81 wgetrc or by the fallback one. */
82
83#define DEFAULT_PROGRESS_IMPLEMENTATION "bar"
84
85/* Fallnback progress implementation should be something that works
86 under all display types. If you put something other than "dot"
87 here, remember that bar_set_params tries to switch to this if we're
88 not running on a TTY. So changing this to "bar" could cause
89 infloop. */
90
91#define FALLBACK_PROGRESS_IMPLEMENTATION "dot"
92
93/* Return non-zero if NAME names a valid progress bar implementation.
94 The characters after the first : will be ignored. */
95
96int
97valid_progress_implementation_p (const char *name)
98{
99 int i;
100 struct progress_implementation *pi = implementations;
101 char *colon = strchr (name, ':');
102 int namelen = colon ? colon - name : strlen (name);
103
104 for (i = 0; i < countof (implementations); i++, pi++)
105 if (!strncmp (pi->name, name, namelen))
106 return 1;
107 return 0;
108}
109
110/* Set the progress implementation to NAME. */
111
112void
113set_progress_implementation (const char *name)
114{
115 int i, namelen;
116 struct progress_implementation *pi = implementations;
117 char *colon;
118
119 if (!name)
120 name = DEFAULT_PROGRESS_IMPLEMENTATION;
121
122 colon = strchr (name, ':');
123 namelen = colon ? colon - name : strlen (name);
124
125 for (i = 0; i < countof (implementations); i++, pi++)
126 if (!strncmp (pi->name, name, namelen))
127 {
128 current_impl = pi;
129 current_impl_locked = 0;
130
131 if (colon)
132 /* We call pi->set_params even if colon is NULL because we
133 want to give the implementation a chance to set up some
134 things it needs to run. */
135 ++colon;
136
137 if (pi->set_params)
138 pi->set_params (colon);
139 return;
140 }
141 abort ();
142}
143
144static int output_redirected;
145
146void
147progress_schedule_redirect (void)
148{
149 output_redirected = 1;
150}
151
152/* Create a progress gauge. INITIAL is the number of bytes the
153 download starts from (zero if the download starts from scratch).
154 TOTAL is the expected total number of bytes in this download. If
155 TOTAL is zero, it means that the download size is not known in
156 advance. */
157
158void *
159progress_create (wgint initial, wgint total)
160{
161 /* Check if the log status has changed under our feet. */
162 if (output_redirected)
163 {
164 if (!current_impl_locked)
165 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
166 output_redirected = 0;
167 }
168
169 return current_impl->create (initial, total);
170}
171
172/* Return non-zero if the progress gauge is "interactive", i.e. if it
173 can profit from being called regularly even in absence of data.
174 The progress bar is interactive because it regularly updates the
175 ETA and current update. */
176
177int
178progress_interactive_p (void *progress)
179{
180 return current_impl->interactive;
181}
182
183/* Inform the progress gauge of newly received bytes. DLTIME is the
184 time in milliseconds since the beginning of the download. */
185
186void
187progress_update (void *progress, wgint howmuch, double dltime)
188{
189 current_impl->update (progress, howmuch, dltime);
190}
191
192/* Tell the progress gauge to clean up. Calling this will free the
193 PROGRESS object, the further use of which is not allowed. */
194
195void
196progress_finish (void *progress, double dltime)
197{
198 current_impl->finish (progress, dltime);
199}
200
201
202/* Dot-printing. */
203
204struct dot_progress {
205 wgint initial_length; /* how many bytes have been downloaded
206 previously. */
207 wgint total_length; /* expected total byte count when the
208 download finishes */
209
210 int accumulated;
211
212 int rows; /* number of rows printed so far */
213 int dots; /* number of dots printed in this row */
214 double last_timer_value;
215};
216
217/* Dot-progress backend for progress_create. */
218
219static void *
220dot_create (wgint initial, wgint total)
221{
222 struct dot_progress *dp = xnew0 (struct dot_progress);
223 dp->initial_length = initial;
224 dp->total_length = total;
225
226 if (dp->initial_length)
227 {
228 int dot_bytes = opt.dot_bytes;
229 wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
230
231 int remainder = (int) (dp->initial_length % row_bytes);
232 wgint skipped = dp->initial_length - remainder;
233
234 if (skipped)
235 {
236 int skipped_k = (int) (skipped / 1024); /* skipped amount in K */
237 int skipped_k_len = numdigit (skipped_k);
238 if (skipped_k_len < 5)
239 skipped_k_len = 5;
240
241 /* Align the [ skipping ... ] line with the dots. To do
242 that, insert the number of spaces equal to the number of
243 digits in the skipped amount in K. */
244 logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"),
245 2 + skipped_k_len, "", skipped_k);
246 }
247
248 logprintf (LOG_VERBOSE, "\n%5ldK", (long) (skipped / 1024));
249 for (; remainder >= dot_bytes; remainder -= dot_bytes)
250 {
251 if (dp->dots % opt.dot_spacing == 0)
252 logputs (LOG_VERBOSE, " ");
253 logputs (LOG_VERBOSE, ",");
254 ++dp->dots;
255 }
256 assert (dp->dots < opt.dots_in_line);
257
258 dp->accumulated = remainder;
259 dp->rows = skipped / row_bytes;
260 }
261
262 return dp;
263}
264
265static void
266print_percentage (wgint bytes, wgint expected)
267{
268 int percentage = (int)(100.0 * bytes / expected);
269 logprintf (LOG_VERBOSE, "%3d%%", percentage);
270}
271
272static void
273print_download_speed (struct dot_progress *dp, wgint bytes, double dltime)
274{
275 logprintf (LOG_VERBOSE, " %s",
276 retr_rate (bytes, dltime - dp->last_timer_value, 1));
277 dp->last_timer_value = dltime;
278}
279
280/* Dot-progress backend for progress_update. */
281
282static void
283dot_update (void *progress, wgint howmuch, double dltime)
284{
285 struct dot_progress *dp = progress;
286 int dot_bytes = opt.dot_bytes;
287 wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
288
289 log_set_flush (0);
290
291 dp->accumulated += howmuch;
292 for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
293 {
294 if (dp->dots == 0)
295 logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024));
296
297 if (dp->dots % opt.dot_spacing == 0)
298 logputs (LOG_VERBOSE, " ");
299 logputs (LOG_VERBOSE, ".");
300
301 ++dp->dots;
302 if (dp->dots >= opt.dots_in_line)
303 {
304 wgint row_qty = row_bytes;
305 if (dp->rows == dp->initial_length / row_bytes)
306 row_qty -= dp->initial_length % row_bytes;
307
308 ++dp->rows;
309 dp->dots = 0;
310
311 if (dp->total_length)
312 print_percentage (dp->rows * row_bytes, dp->total_length);
313 print_download_speed (dp, row_qty, dltime);
314 }
315 }
316
317 log_set_flush (1);
318}
319
320/* Dot-progress backend for progress_finish. */
321
322static void
323dot_finish (void *progress, double dltime)
324{
325 struct dot_progress *dp = progress;
326 int dot_bytes = opt.dot_bytes;
327 wgint row_bytes = opt.dot_bytes * opt.dots_in_line;
328 int i;
329
330 log_set_flush (0);
331
332 if (dp->dots == 0)
333 logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024));
334 for (i = dp->dots; i < opt.dots_in_line; i++)
335 {
336 if (i % opt.dot_spacing == 0)
337 logputs (LOG_VERBOSE, " ");
338 logputs (LOG_VERBOSE, " ");
339 }
340 if (dp->total_length)
341 {
342 print_percentage (dp->rows * row_bytes
343 + dp->dots * dot_bytes
344 + dp->accumulated,
345 dp->total_length);
346 }
347
348 {
349 wgint row_qty = dp->dots * dot_bytes + dp->accumulated;
350 if (dp->rows == dp->initial_length / row_bytes)
351 row_qty -= dp->initial_length % row_bytes;
352 print_download_speed (dp, row_qty, dltime);
353 }
354
355 logputs (LOG_VERBOSE, "\n\n");
356 log_set_flush (0);
357
358 xfree (dp);
359}
360
361/* This function interprets the progress "parameters". For example,
362 if Wget is invoked with --progress=dot:mega, it will set the
363 "dot-style" to "mega". Valid styles are default, binary, mega, and
364 giga. */
365
366static void
367dot_set_params (const char *params)
368{
369 if (!params || !*params)
370 params = opt.dot_style;
371
372 if (!params)
373 return;
374
375 /* We use this to set the retrieval style. */
376 if (!strcasecmp (params, "default"))
377 {
378 /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
379 line. */
380 opt.dot_bytes = 1024;
381 opt.dot_spacing = 10;
382 opt.dots_in_line = 50;
383 }
384 else if (!strcasecmp (params, "binary"))
385 {
386 /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
387 (384K) in a line. */
388 opt.dot_bytes = 8192;
389 opt.dot_spacing = 16;
390 opt.dots_in_line = 48;
391 }
392 else if (!strcasecmp (params, "mega"))
393 {
394 /* "Mega" retrieval, for retrieving very long files; each dot is
395 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
396 opt.dot_bytes = 65536L;
397 opt.dot_spacing = 8;
398 opt.dots_in_line = 48;
399 }
400 else if (!strcasecmp (params, "giga"))
401 {
402 /* "Giga" retrieval, for retrieving very very *very* long files;
403 each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
404 line. */
405 opt.dot_bytes = (1L << 20);
406 opt.dot_spacing = 8;
407 opt.dots_in_line = 32;
408 }
409 else
410 fprintf (stderr,
411 _("Invalid dot style specification `%s'; leaving unchanged.\n"),
412 params);
413}
414
415
416/* "Thermometer" (bar) progress. */
417
418/* Assumed screen width if we can't find the real value. */
419#define DEFAULT_SCREEN_WIDTH 80
420
421/* Minimum screen width we'll try to work with. If this is too small,
422 create_image will overflow the buffer. */
423#define MINIMUM_SCREEN_WIDTH 45
424
425/* The last known screen width. This can be updated by the code that
426 detects that SIGWINCH was received (but it's never updated from the
427 signal handler). */
428static int screen_width;
429
430/* A flag that, when set, means SIGWINCH was received. */
431static volatile sig_atomic_t received_sigwinch;
432
433/* Size of the download speed history ring. */
434#define DLSPEED_HISTORY_SIZE 20
435
436/* The minimum time length of a history sample. By default, each
437 sample is at least 150ms long, which means that, over the course of
438 20 samples, "current" download speed spans at least 3s into the
439 past. */
440#define DLSPEED_SAMPLE_MIN 150
441
442/* The time after which the download starts to be considered
443 "stalled", i.e. the current bandwidth is not printed and the recent
444 download speeds are scratched. */
445#define STALL_START_TIME 5000
446
447struct bar_progress {
448 wgint initial_length; /* how many bytes have been downloaded
449 previously. */
450 wgint total_length; /* expected total byte count when the
451 download finishes */
452 wgint count; /* bytes downloaded so far */
453
454 double last_screen_update; /* time of the last screen update,
455 measured since the beginning of
456 download. */
457
458 int width; /* screen width we're using at the
459 time the progress gauge was
460 created. this is different from
461 the screen_width global variable in
462 that the latter can be changed by a
463 signal. */
464 char *buffer; /* buffer where the bar "image" is
465 stored. */
466 int tick; /* counter used for drawing the
467 progress bar where the total size
468 is not known. */
469
470 /* The following variables (kept in a struct for namespace reasons)
471 keep track of recent download speeds. See bar_update() for
472 details. */
473 struct bar_progress_hist {
474 int pos;
475 wgint times[DLSPEED_HISTORY_SIZE];
476 wgint bytes[DLSPEED_HISTORY_SIZE];
477
478 /* The sum of times and bytes respectively, maintained for
479 efficiency. */
480 wgint total_time;
481 wgint total_bytes;
482 } hist;
483
484 double recent_start; /* timestamp of beginning of current
485 position. */
486 wgint recent_bytes; /* bytes downloaded so far. */
487
488 int stalled; /* set when no data arrives for longer
489 than STALL_START_TIME, then reset
490 when new data arrives. */
491
492 /* create_image() uses these to make sure that ETA information
493 doesn't flicker. */
494 double last_eta_time; /* time of the last update to download
495 speed and ETA, measured since the
496 beginning of download. */
497 wgint last_eta_value;
498};
499
500static void create_image PARAMS ((struct bar_progress *, double));
501static void display_image PARAMS ((char *));
502
503static void *
504bar_create (wgint initial, wgint total)
505{
506 struct bar_progress *bp = xnew0 (struct bar_progress);
507
508 /* In theory, our callers should take care of this pathological
509 case, but it can sometimes happen. */
510 if (initial > total)
511 total = initial;
512
513 bp->initial_length = initial;
514 bp->total_length = total;
515
516 /* Initialize screen_width if this hasn't been done or if it might
517 have changed, as indicated by receiving SIGWINCH. */
518 if (!screen_width || received_sigwinch)
519 {
520 screen_width = determine_screen_width ();
521 if (!screen_width)
522 screen_width = DEFAULT_SCREEN_WIDTH;
523 else if (screen_width < MINIMUM_SCREEN_WIDTH)
524 screen_width = MINIMUM_SCREEN_WIDTH;
525 received_sigwinch = 0;
526 }
527
528 /* - 1 because we don't want to use the last screen column. */
529 bp->width = screen_width - 1;
530 /* + 1 for the terminating zero. */
531 bp->buffer = xmalloc (bp->width + 1);
532
533 logputs (LOG_VERBOSE, "\n");
534
535 create_image (bp, 0.0);
536 display_image (bp->buffer);
537
538 return bp;
539}
540
541static void update_speed_ring PARAMS ((struct bar_progress *, wgint, double));
542
543static void
544bar_update (void *progress, wgint howmuch, double dltime)
545{
546 struct bar_progress *bp = progress;
547 int force_screen_update = 0;
548
549 bp->count += howmuch;
550 if (bp->total_length > 0
551 && bp->count + bp->initial_length > bp->total_length)
552 /* We could be downloading more than total_length, e.g. when the
553 server sends an incorrect Content-Length header. In that case,
554 adjust bp->total_length to the new reality, so that the code in
555 create_image() that depends on total size being smaller or
556 equal to the expected size doesn't abort. */
557 bp->total_length = bp->initial_length + bp->count;
558
559 update_speed_ring (bp, howmuch, dltime);
560
561 /* If SIGWINCH (the window size change signal) been received,
562 determine the new screen size and update the screen. */
563 if (received_sigwinch)
564 {
565 int old_width = screen_width;
566 screen_width = determine_screen_width ();
567 if (!screen_width)
568 screen_width = DEFAULT_SCREEN_WIDTH;
569 else if (screen_width < MINIMUM_SCREEN_WIDTH)
570 screen_width = MINIMUM_SCREEN_WIDTH;
571 if (screen_width != old_width)
572 {
573 bp->width = screen_width - 1;
574 bp->buffer = xrealloc (bp->buffer, bp->width + 1);
575 force_screen_update = 1;
576 }
577 received_sigwinch = 0;
578 }
579
580 if (dltime - bp->last_screen_update < 200 && !force_screen_update)
581 /* Don't update more often than five times per second. */
582 return;
583
584 create_image (bp, dltime);
585 display_image (bp->buffer);
586 bp->last_screen_update = dltime;
587}
588
589static void
590bar_finish (void *progress, double dltime)
591{
592 struct bar_progress *bp = progress;
593
594 if (bp->total_length > 0
595 && bp->count + bp->initial_length > bp->total_length)
596 /* See bar_update() for explanation. */
597 bp->total_length = bp->initial_length + bp->count;
598
599 create_image (bp, dltime);
600 display_image (bp->buffer);
601
602 logputs (LOG_VERBOSE, "\n\n");
603
604 xfree (bp->buffer);
605 xfree (bp);
606}
607
608/* This code attempts to maintain the notion of a "current" download
609 speed, over the course of no less than 3s. (Shorter intervals
610 produce very erratic results.)
611
612 To do so, it samples the speed in 150ms intervals and stores the
613 recorded samples in a FIFO history ring. The ring stores no more
614 than 20 intervals, hence the history covers the period of at least
615 three seconds and at most 20 reads into the past. This method
616 should produce reasonable results for downloads ranging from very
617 slow to very fast.
618
619 The idea is that for fast downloads, we get the speed over exactly
620 the last three seconds. For slow downloads (where a network read
621 takes more than 150ms to complete), we get the speed over a larger
622 time period, as large as it takes to complete thirty reads. This
623 is good because slow downloads tend to fluctuate more and a
624 3-second average would be too erratic. */
625
626static void
627update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime)
628{
629 struct bar_progress_hist *hist = &bp->hist;
630 double recent_age = dltime - bp->recent_start;
631
632 /* Update the download count. */
633 bp->recent_bytes += howmuch;
634
635 /* For very small time intervals, we return after having updated the
636 "recent" download count. When its age reaches or exceeds minimum
637 sample time, it will be recorded in the history ring. */
638 if (recent_age < DLSPEED_SAMPLE_MIN)
639 return;
640
641 if (howmuch == 0)
642 {
643 /* If we're not downloading anything, we might be stalling,
644 i.e. not downloading anything for an extended period of time.
645 Since 0-reads do not enter the history ring, recent_age
646 effectively measures the time since last read. */
647 if (recent_age >= STALL_START_TIME)
648 {
649 /* If we're stalling, reset the ring contents because it's
650 stale and because it will make bar_update stop printing
651 the (bogus) current bandwidth. */
652 bp->stalled = 1;
653 xzero (*hist);
654 bp->recent_bytes = 0;
655 }
656 return;
657 }
658
659 /* We now have a non-zero amount of to store to the speed ring. */
660
661 /* If the stall status was acquired, reset it. */
662 if (bp->stalled)
663 {
664 bp->stalled = 0;
665 /* "recent_age" includes the the entired stalled period, which
666 could be very long. Don't update the speed ring with that
667 value because the current bandwidth would start too small.
668 Start with an arbitrary (but more reasonable) time value and
669 let it level out. */
670 recent_age = 1000;
671 }
672
673 /* Store "recent" bytes and download time to history ring at the
674 position POS. */
675
676 /* To correctly maintain the totals, first invalidate existing data
677 (least recent in time) at this position. */
678 hist->total_time -= hist->times[hist->pos];
679 hist->total_bytes -= hist->bytes[hist->pos];
680
681 /* Now store the new data and update the totals. */
682 hist->times[hist->pos] = recent_age;
683 hist->bytes[hist->pos] = bp->recent_bytes;
684 hist->total_time += recent_age;
685 hist->total_bytes += bp->recent_bytes;
686
687 /* Start a new "recent" period. */
688 bp->recent_start = dltime;
689 bp->recent_bytes = 0;
690
691 /* Advance the current ring position. */
692 if (++hist->pos == DLSPEED_HISTORY_SIZE)
693 hist->pos = 0;
694
695#if 0
696 /* Sledgehammer check to verify that the totals are accurate. */
697 {
698 int i;
699 double sumt = 0, sumb = 0;
700 for (i = 0; i < DLSPEED_HISTORY_SIZE; i++)
701 {
702 sumt += hist->times[i];
703 sumb += hist->bytes[i];
704 }
705 assert (sumt == hist->total_time);
706 assert (sumb == hist->total_bytes);
707 }
708#endif
709}
710
711#define APPEND_LITERAL(s) do { \
712 memcpy (p, s, sizeof (s) - 1); \
713 p += sizeof (s) - 1; \
714} while (0)
715
716#ifndef MAX
717# define MAX(a, b) ((a) >= (b) ? (a) : (b))
718#endif
719
720static void
721create_image (struct bar_progress *bp, double dl_total_time)
722{
723 char *p = bp->buffer;
724 wgint size = bp->initial_length + bp->count;
725
726 char *size_legible = with_thousand_seps (size);
727 int size_legible_len = strlen (size_legible);
728
729 struct bar_progress_hist *hist = &bp->hist;
730
731 /* The progress bar should look like this:
732 xx% [=======> ] nn,nnn 12.34K/s ETA 00:00
733
734 Calculate the geometry. The idea is to assign as much room as
735 possible to the progress bar. The other idea is to never let
736 things "jitter", i.e. pad elements that vary in size so that
737 their variance does not affect the placement of other elements.
738 It would be especially bad for the progress bar to be resized
739 randomly.
740
741 "xx% " or "100%" - percentage - 4 chars
742 "[]" - progress bar decorations - 2 chars
743 " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more
744 " 1012.56K/s" - dl rate - 11 chars
745 " ETA xx:xx:xx" - ETA - 13 chars
746
747 "=====>..." - progress bar - the rest
748 */
749 int dlbytes_size = 1 + MAX (size_legible_len, 11);
750 int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13);
751
752 if (progress_size < 5)
753 progress_size = 0;
754
755 /* "xx% " */
756 if (bp->total_length > 0)
757 {
758 int percentage = (int)(100.0 * size / bp->total_length);
759
760 assert (percentage <= 100);
761
762 if (percentage < 100)
763 sprintf (p, "%2d%% ", percentage);
764 else
765 strcpy (p, "100%");
766 p += 4;
767 }
768 else
769 APPEND_LITERAL (" ");
770
771 /* The progress bar: "[====> ]" or "[++==> ]". */
772 if (progress_size && bp->total_length > 0)
773 {
774 /* Size of the initial portion. */
775 int insz = (double)bp->initial_length / bp->total_length * progress_size;
776
777 /* Size of the downloaded portion. */
778 int dlsz = (double)size / bp->total_length * progress_size;
779
780 char *begin;
781 int i;
782
783 assert (dlsz <= progress_size);
784 assert (insz <= dlsz);
785
786 *p++ = '[';
787 begin = p;
788
789 /* Print the initial portion of the download with '+' chars, the
790 rest with '=' and one '>'. */
791 for (i = 0; i < insz; i++)
792 *p++ = '+';
793 dlsz -= insz;
794 if (dlsz > 0)
795 {
796 for (i = 0; i < dlsz - 1; i++)
797 *p++ = '=';
798 *p++ = '>';
799 }
800
801 while (p - begin < progress_size)
802 *p++ = ' ';
803 *p++ = ']';
804 }
805 else if (progress_size)
806 {
807 /* If we can't draw a real progress bar, then at least show
808 *something* to the user. */
809 int ind = bp->tick % (progress_size * 2 - 6);
810 int i, pos;
811
812 /* Make the star move in two directions. */
813 if (ind < progress_size - 2)
814 pos = ind + 1;
815 else
816 pos = progress_size - (ind - progress_size + 5);
817
818 *p++ = '[';
819 for (i = 0; i < progress_size; i++)
820 {
821 if (i == pos - 1) *p++ = '<';
822 else if (i == pos ) *p++ = '=';
823 else if (i == pos + 1) *p++ = '>';
824 else
825 *p++ = ' ';
826 }
827 *p++ = ']';
828
829 ++bp->tick;
830 }
831
832 /* " 234,567,890" */
833 sprintf (p, " %-11s", size_legible);
834 p += strlen (p);
835
836 /* " 1012.45K/s" */
837 if (hist->total_time && hist->total_bytes)
838 {
839 static const char *short_units[] = { "B/s", "K/s", "M/s", "G/s" };
840 int units = 0;
841 /* Calculate the download speed using the history ring and
842 recent data that hasn't made it to the ring yet. */
843 wgint dlquant = hist->total_bytes + bp->recent_bytes;
844 double dltime = hist->total_time + (dl_total_time - bp->recent_start);
845 double dlspeed = calc_rate (dlquant, dltime, &units);
846 sprintf (p, " %7.2f%s", dlspeed, short_units[units]);
847 p += strlen (p);
848 }
849 else
850 APPEND_LITERAL (" --.--K/s");
851
852 /* " ETA xx:xx:xx"; wait for three seconds before displaying the ETA.
853 That's because the ETA value needs a while to become
854 reliable. */
855 if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3000)
856 {
857 wgint eta;
858 int eta_hrs, eta_min, eta_sec;
859
860 /* Don't change the value of ETA more than approximately once
861 per second; doing so would cause flashing without providing
862 any value to the user. */
863 if (bp->total_length != size
864 && bp->last_eta_value != 0
865 && dl_total_time - bp->last_eta_time < 900)
866 eta = bp->last_eta_value;
867 else
868 {
869 /* Calculate ETA using the average download speed to predict
870 the future speed. If you want to use a speed averaged
871 over a more recent period, replace dl_total_time with
872 hist->total_time and bp->count with hist->total_bytes.
873 I found that doing that results in a very jerky and
874 ultimately unreliable ETA. */
875 double time_sofar = (double)dl_total_time / 1000;
876 wgint bytes_remaining = bp->total_length - size;
877 eta = (wgint) (time_sofar * bytes_remaining / bp->count);
878 bp->last_eta_value = eta;
879 bp->last_eta_time = dl_total_time;
880 }
881
882 eta_hrs = eta / 3600, eta %= 3600;
883 eta_min = eta / 60, eta %= 60;
884 eta_sec = eta;
885
886 if (eta_hrs > 99)
887 goto no_eta;
888
889 if (eta_hrs == 0)
890 {
891 /* Hours not printed: pad with three spaces. */
892 APPEND_LITERAL (" ");
893 sprintf (p, " ETA %02d:%02d", eta_min, eta_sec);
894 }
895 else
896 {
897 if (eta_hrs < 10)
898 /* Hours printed with one digit: pad with one space. */
899 *p++ = ' ';
900 sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec);
901 }
902 p += strlen (p);
903 }
904 else if (bp->total_length > 0)
905 {
906 no_eta:
907 APPEND_LITERAL (" ");
908 }
909
910 assert (p - bp->buffer <= bp->width);
911
912 while (p < bp->buffer + bp->width)
913 *p++ = ' ';
914 *p = '\0';
915}
916
917/* Print the contents of the buffer as a one-line ASCII "image" so
918 that it can be overwritten next time. */
919
920static void
921display_image (char *buf)
922{
923 int old = log_set_save_context (0);
924 logputs (LOG_VERBOSE, "\r");
925 logputs (LOG_VERBOSE, buf);
926 log_set_save_context (old);
927}
928
929static void
930bar_set_params (const char *params)
931{
932 char *term = getenv ("TERM");
933
934 if (params
935 && 0 == strcmp (params, "force"))
936 current_impl_locked = 1;
937
938 if ((opt.lfilename
939#ifdef HAVE_ISATTY
940 /* The progress bar doesn't make sense if the output is not a
941 TTY -- when logging to file, it is better to review the
942 dots. */
943 || !isatty (fileno (stderr))
944#endif
945 /* Normally we don't depend on terminal type because the
946 progress bar only uses ^M to move the cursor to the
947 beginning of line, which works even on dumb terminals. But
948 Jamie Zawinski reports that ^M and ^H tricks don't work in
949 Emacs shell buffers, and only make a mess. */
950 || (term && 0 == strcmp (term, "emacs"))
951 )
952 && !current_impl_locked)
953 {
954 /* We're not printing to a TTY, so revert to the fallback
955 display. #### We're recursively calling
956 set_progress_implementation here, which is slightly kludgy.
957 It would be nicer if we provided that function a return value
958 indicating a failure of some sort. */
959 set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION);
960 return;
961 }
962}
963
964#ifdef SIGWINCH
965RETSIGTYPE
966progress_handle_sigwinch (int sig)
967{
968 received_sigwinch = 1;
969 signal (SIGWINCH, progress_handle_sigwinch);
970}
971#endif
Note: See TracBrowser for help on using the repository browser.