Line | Count | Source (jump to first uncovered line) |
1 | | /* Download progress. |
2 | | Copyright (C) 2001-2011, 2015, 2018-2024 Free Software Foundation, |
3 | | Inc. |
4 | | |
5 | | This file is part of GNU Wget. |
6 | | |
7 | | GNU Wget is free software; you can redistribute it and/or modify |
8 | | it under the terms of the GNU General Public License as published by |
9 | | the Free Software Foundation; either version 3 of the License, or |
10 | | (at your option) any later version. |
11 | | |
12 | | GNU Wget is distributed in the hope that it will be useful, |
13 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | GNU General Public License for more details. |
16 | | |
17 | | You should have received a copy of the GNU General Public License |
18 | | along with Wget. If not, see <http://www.gnu.org/licenses/>. |
19 | | |
20 | | Additional permission under GNU GPL version 3 section 7 |
21 | | |
22 | | If you modify this program, or any covered work, by linking or |
23 | | combining it with the OpenSSL project's OpenSSL library (or a |
24 | | modified version of that library), containing parts covered by the |
25 | | terms of the OpenSSL or SSLeay licenses, the Free Software Foundation |
26 | | grants you additional permission to convey the resulting work. |
27 | | Corresponding Source for a non-source form of such a combination |
28 | | shall include the source code for the parts of OpenSSL used as well |
29 | | as that of the covered work. */ |
30 | | |
31 | | #include "wget.h" |
32 | | |
33 | | #include <stdio.h> |
34 | | #include <stdlib.h> |
35 | | #include <string.h> |
36 | | #include <assert.h> |
37 | | #include <unistd.h> |
38 | | #include <signal.h> |
39 | | #include <wchar.h> |
40 | | #include <mbiter.h> |
41 | | |
42 | | #include "progress.h" |
43 | | #include "utils.h" |
44 | | #include "retr.h" |
45 | | #include "c-strcase.h" |
46 | | |
47 | | struct progress_implementation { |
48 | | const char *name; |
49 | | bool interactive; |
50 | | void *(*create) (const char *, wgint, wgint); |
51 | | void (*update) (void *, wgint, double); |
52 | | void (*draw) (void *); |
53 | | void (*finish) (void *, double); |
54 | | void (*set_params) (const char *); |
55 | | }; |
56 | | |
57 | | /* Necessary forward declarations. */ |
58 | | |
59 | | static void *dot_create (const char *, wgint, wgint); |
60 | | static void dot_update (void *, wgint, double); |
61 | | static void dot_finish (void *, double); |
62 | | static void dot_draw (void *); |
63 | | static void dot_set_params (const char *); |
64 | | |
65 | | static void *bar_create (const char *, wgint, wgint); |
66 | | static void bar_update (void *, wgint, double); |
67 | | static void bar_draw (void *); |
68 | | static void bar_finish (void *, double); |
69 | | static void bar_set_params (const char *); |
70 | | |
71 | | static struct progress_implementation implementations[] = { |
72 | | { "dot", 0, dot_create, dot_update, dot_draw, dot_finish, dot_set_params }, |
73 | | { "bar", 1, bar_create, bar_update, bar_draw, bar_finish, bar_set_params } |
74 | | }; |
75 | | static struct progress_implementation *current_impl; |
76 | | static int current_impl_locked; |
77 | | |
78 | | /* Progress implementation used by default. Can be overridden in |
79 | | wgetrc or by the fallback one. */ |
80 | | |
81 | 0 | #define DEFAULT_PROGRESS_IMPLEMENTATION "bar" |
82 | | |
83 | | /* Fallback progress implementation should be something that works |
84 | | under all display types. If you put something other than "dot" |
85 | | here, remember that bar_set_params tries to switch to this if we're |
86 | | not running on a TTY. So changing this to "bar" could cause |
87 | | infloop. */ |
88 | | |
89 | 0 | #define FALLBACK_PROGRESS_IMPLEMENTATION "dot" |
90 | | |
91 | | /* Return true if NAME names a valid progress bar implementation. The |
92 | | characters after the first : will be ignored. */ |
93 | | |
94 | | bool |
95 | | valid_progress_implementation_p (const char *name) |
96 | 0 | { |
97 | 0 | size_t i; |
98 | 0 | struct progress_implementation *pi = implementations; |
99 | 0 | char *colon = strchr (name, ':'); |
100 | 0 | size_t namelen = colon ? (size_t) (colon - name) : strlen (name); |
101 | |
|
102 | 0 | for (i = 0; i < countof (implementations); i++, pi++) |
103 | 0 | if (!strncmp (pi->name, name, namelen)) |
104 | 0 | return true; |
105 | 0 | return false; |
106 | 0 | } |
107 | | |
108 | | /* Set the progress implementation to NAME. */ |
109 | | |
110 | | void |
111 | | set_progress_implementation (const char *name) |
112 | 0 | { |
113 | 0 | size_t i, namelen; |
114 | 0 | struct progress_implementation *pi = implementations; |
115 | 0 | const char *colon; |
116 | |
|
117 | 0 | if (!name) |
118 | 0 | name = DEFAULT_PROGRESS_IMPLEMENTATION; |
119 | |
|
120 | 0 | colon = strchr (name, ':'); |
121 | 0 | namelen = colon ? (size_t) (colon - name) : strlen (name); |
122 | |
|
123 | 0 | for (i = 0; i < countof (implementations); i++, pi++) |
124 | 0 | if (!strncmp (pi->name, name, namelen)) |
125 | 0 | { |
126 | 0 | current_impl = pi; |
127 | 0 | current_impl_locked = 0; |
128 | |
|
129 | 0 | if (colon) |
130 | | /* We call pi->set_params even if colon is NULL because we |
131 | | want to give the implementation a chance to set up some |
132 | | things it needs to run. */ |
133 | 0 | ++colon; |
134 | |
|
135 | 0 | if (pi->set_params) |
136 | 0 | pi->set_params (colon); |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | abort (); |
140 | 0 | } |
141 | | |
142 | | static int output_redirected; |
143 | | |
144 | | void |
145 | | progress_schedule_redirect (void) |
146 | 0 | { |
147 | 0 | output_redirected = 1; |
148 | 0 | } |
149 | | |
150 | | /* Create a progress gauge. INITIAL is the number of bytes the |
151 | | download starts from (zero if the download starts from scratch). |
152 | | TOTAL is the expected total number of bytes in this download. If |
153 | | TOTAL is zero, it means that the download size is not known in |
154 | | advance. */ |
155 | | |
156 | | void * |
157 | | progress_create (const char *f_download, wgint initial, wgint total) |
158 | 0 | { |
159 | | /* Check if the log status has changed under our feet. */ |
160 | 0 | if (output_redirected) |
161 | 0 | { |
162 | 0 | if (!current_impl_locked) |
163 | 0 | set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION); |
164 | 0 | output_redirected = 0; |
165 | 0 | } |
166 | |
|
167 | 0 | return current_impl->create (f_download, initial, total); |
168 | 0 | } |
169 | | |
170 | | /* Return true if the progress gauge is "interactive", i.e. if it can |
171 | | profit from being called regularly even in absence of data. The |
172 | | progress bar is interactive because it regularly updates the ETA |
173 | | and current update. */ |
174 | | |
175 | | bool |
176 | | progress_interactive_p (void *progress _GL_UNUSED) |
177 | 0 | { |
178 | 0 | return current_impl->interactive; |
179 | 0 | } |
180 | | |
181 | | /* Inform the progress gauge of newly received bytes. DLTIME is the |
182 | | time since the beginning of the download. */ |
183 | | |
184 | | void |
185 | | progress_update (void *progress, wgint howmuch, double dltime) |
186 | 0 | { |
187 | | // sanitize input |
188 | 0 | if (dltime >= INT_MAX) |
189 | 0 | dltime = INT_MAX - 1; |
190 | 0 | else if (dltime < 0) |
191 | 0 | dltime = 0; |
192 | |
|
193 | 0 | if (howmuch < 0) |
194 | 0 | howmuch = 0; |
195 | |
|
196 | 0 | current_impl->update (progress, howmuch, dltime); |
197 | 0 | current_impl->draw (progress); |
198 | 0 | } |
199 | | |
200 | | /* Tell the progress gauge to clean up. Calling this will free the |
201 | | PROGRESS object, the further use of which is not allowed. */ |
202 | | |
203 | | void |
204 | | progress_finish (void *progress, double dltime) |
205 | 0 | { |
206 | | // sanitize input |
207 | 0 | if (dltime >= INT_MAX) |
208 | 0 | dltime = INT_MAX - 1; |
209 | 0 | else if (dltime < 0) |
210 | 0 | dltime = 0; |
211 | |
|
212 | 0 | current_impl->finish (progress, dltime); |
213 | 0 | } |
214 | | |
215 | | /* Dot-printing. */ |
216 | | |
217 | | struct dot_progress { |
218 | | wgint initial_length; /* how many bytes have been downloaded |
219 | | previously. */ |
220 | | wgint total_length; /* expected total byte count when the |
221 | | download finishes */ |
222 | | |
223 | | wgint accumulated; /* number of bytes accumulated after |
224 | | the last printed dot */ |
225 | | |
226 | | double dltime; /* download time so far */ |
227 | | wgint rows; /* number of rows printed so far */ |
228 | | int dots; /* number of dots printed in this row */ |
229 | | |
230 | | double last_timer_value; |
231 | | }; |
232 | | |
233 | | /* Dot-progress backend for progress_create. */ |
234 | | |
235 | | static void * |
236 | | dot_create (const char *f_download _GL_UNUSED, wgint initial, wgint total) |
237 | 0 | { |
238 | 0 | struct dot_progress *dp = xnew0 (struct dot_progress); |
239 | 0 | dp->initial_length = initial; |
240 | 0 | dp->total_length = total; |
241 | |
|
242 | 0 | if (dp->initial_length) |
243 | 0 | { |
244 | 0 | int dot_bytes = opt.dot_bytes; |
245 | 0 | const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; |
246 | |
|
247 | 0 | int remainder = dp->initial_length % ROW_BYTES; |
248 | 0 | wgint skipped = dp->initial_length - remainder; |
249 | |
|
250 | 0 | if (skipped) |
251 | 0 | { |
252 | 0 | wgint skipped_k = skipped / 1024; /* skipped amount in K */ |
253 | 0 | int skipped_k_len = numdigit (skipped_k); |
254 | 0 | if (skipped_k_len < 6) |
255 | 0 | skipped_k_len = 6; |
256 | | |
257 | | /* Align the [ skipping ... ] line with the dots. To do |
258 | | that, insert the number of spaces equal to the number of |
259 | | digits in the skipped amount in K. */ |
260 | 0 | logprintf (LOG_PROGRESS, _("\n%*s[ skipping %sK ]"), |
261 | 0 | 2 + skipped_k_len, "", |
262 | 0 | number_to_static_string (skipped_k)); |
263 | 0 | } |
264 | |
|
265 | 0 | logprintf (LOG_PROGRESS, "\n%6sK", |
266 | 0 | number_to_static_string (skipped / 1024)); |
267 | 0 | for (; remainder >= dot_bytes; remainder -= dot_bytes) |
268 | 0 | { |
269 | 0 | if (dp->dots % opt.dot_spacing == 0) |
270 | 0 | logputs (LOG_PROGRESS, " "); |
271 | 0 | logputs (LOG_PROGRESS, ","); |
272 | 0 | ++dp->dots; |
273 | 0 | } |
274 | 0 | assert (dp->dots < opt.dots_in_line); |
275 | |
|
276 | 0 | dp->accumulated = remainder; |
277 | 0 | dp->rows = skipped / ROW_BYTES; |
278 | 0 | } |
279 | |
|
280 | 0 | return dp; |
281 | 0 | } |
282 | | |
283 | | static const char *eta_to_human_short (int, bool); |
284 | | |
285 | | /* ADD_DOT_ROWS_THRS - minimal (1 << ADD_DOT_ROWS_THRS) ROWS to be added |
286 | | to the current row if dp->accumulated too much. |
287 | | Allows to reduce dot_draw io, times. |
288 | | According to the way progress_update is currently has being called, this |
289 | | should happens only when fuzzing, or (paranoia) if somehow buffer will |
290 | | be too large. |
291 | | Can be disabled by default if this is not fuzzing build. */ |
292 | | #ifndef ADD_DOT_ROWS_THRS |
293 | | #if FUZZING |
294 | 0 | #define ADD_DOT_ROWS_THRS 2 |
295 | | #else |
296 | | #define ADD_DOT_ROWS_THRS 2 |
297 | | #endif |
298 | | #endif /* ADD_DOT_ROWS_THRS */ |
299 | | |
300 | | /* Prints the stats (percentage of completion, speed, ETA) for current |
301 | | row. DLTIME is the time spent downloading the data in current |
302 | | row. |
303 | | |
304 | | #### This function is somewhat uglified by the fact that current |
305 | | row and last row have somewhat different stats requirements. It |
306 | | might be worthwhile to split it to two different functions. */ |
307 | | |
308 | | static void |
309 | | #if ADD_DOT_ROWS_THRS |
310 | | print_row_stats (struct dot_progress *dp, double dltime, bool last, wgint added_rows) |
311 | | #else |
312 | | print_row_stats (struct dot_progress *dp, double dltime, bool last) |
313 | | #endif |
314 | 0 | { |
315 | 0 | const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; |
316 | | |
317 | | /* bytes_displayed is the number of bytes indicated to the user by |
318 | | dots printed so far, includes the initially "skipped" amount */ |
319 | 0 | wgint bytes_displayed = dp->rows * ROW_BYTES + dp->dots * opt.dot_bytes; |
320 | |
|
321 | 0 | if (last) |
322 | | /* For last row also count bytes accumulated after last dot */ |
323 | 0 | bytes_displayed += dp->accumulated; |
324 | |
|
325 | 0 | if (bytes_displayed < 0) |
326 | 0 | bytes_displayed = 0; |
327 | |
|
328 | 0 | if (dp->total_length) |
329 | 0 | { |
330 | | /* Round to floor value to provide gauge how much data *has* |
331 | | been retrieved. 12.8% will round to 12% because the 13% mark |
332 | | has not yet been reached. 100% is only shown when done. */ |
333 | 0 | int percentage = 100.0 * bytes_displayed / dp->total_length; |
334 | 0 | logprintf (LOG_PROGRESS, "%3d%%", percentage); |
335 | 0 | } |
336 | |
|
337 | 0 | { |
338 | 0 | static char names[] = {' ', 'K', 'M', 'G', 'T'}; |
339 | 0 | int units; |
340 | 0 | double rate; |
341 | 0 | wgint bytes_this_row; |
342 | 0 | if (!last) |
343 | 0 | #if ADD_DOT_ROWS_THRS |
344 | 0 | bytes_this_row = ROW_BYTES * added_rows; |
345 | | #else |
346 | | bytes_this_row = ROW_BYTES; |
347 | | #endif |
348 | 0 | else |
349 | | /* For last row also include bytes accumulated after last dot. */ |
350 | 0 | bytes_this_row = dp->dots * opt.dot_bytes + dp->accumulated; |
351 | | /* Don't count the portion of the row belonging to initial_length */ |
352 | 0 | if (dp->rows == dp->initial_length / ROW_BYTES) |
353 | 0 | bytes_this_row -= dp->initial_length % ROW_BYTES; |
354 | 0 | rate = calc_rate (bytes_this_row, dltime - dp->last_timer_value, &units); |
355 | 0 | logprintf (LOG_PROGRESS, " %4.*f%c", |
356 | 0 | rate >= 99.95 ? 0 : rate >= 9.995 ? 1 : 2, |
357 | 0 | rate, names[units]); |
358 | 0 | dp->last_timer_value = dltime; |
359 | 0 | } |
360 | |
|
361 | 0 | if (!last) |
362 | 0 | { |
363 | | /* Display ETA based on average speed. Inspired by Vladi |
364 | | Belperchinov-Shabanski's "wget-new-percentage" patch. */ |
365 | 0 | if (dp->total_length) |
366 | 0 | { |
367 | 0 | wgint bytes_remaining = dp->total_length > bytes_displayed ? dp->total_length - bytes_displayed : 0; |
368 | | /* The quantity downloaded in this download run. */ |
369 | 0 | wgint bytes_sofar = bytes_displayed > dp->initial_length ? bytes_displayed - dp->initial_length : 1; |
370 | 0 | double eta = dltime * bytes_remaining / bytes_sofar; |
371 | 0 | if (eta < 0) |
372 | 0 | eta = 0; |
373 | 0 | if (eta < INT_MAX - 1) |
374 | 0 | logprintf (LOG_PROGRESS, " %s", |
375 | 0 | eta_to_human_short ((int) (eta + 0.5), true)); |
376 | 0 | } |
377 | 0 | } |
378 | 0 | else |
379 | 0 | { |
380 | | /* When done, print the total download time */ |
381 | 0 | if (dltime >= 10) |
382 | 0 | logprintf (LOG_PROGRESS, "=%s", |
383 | 0 | eta_to_human_short ((int) (dltime + 0.5), true)); |
384 | 0 | else |
385 | 0 | logprintf (LOG_PROGRESS, "=%ss", print_decimal (dltime)); |
386 | 0 | } |
387 | 0 | } |
388 | | |
389 | | /* Dot-progress backend for progress_update. */ |
390 | | |
391 | | static void |
392 | | dot_update (void *progress, wgint howmuch, double dltime) |
393 | 0 | { |
394 | | // sanitize input |
395 | 0 | if (dltime >= INT_MAX) |
396 | 0 | dltime = INT_MAX - 1; |
397 | 0 | else if (dltime < 0) |
398 | 0 | dltime = 0; |
399 | |
|
400 | 0 | if (howmuch < 0) |
401 | 0 | howmuch = 0; |
402 | |
|
403 | 0 | struct dot_progress *dp = progress; |
404 | 0 | dp->accumulated += howmuch; |
405 | 0 | dp->dltime = dltime; |
406 | 0 | } |
407 | | |
408 | | static void |
409 | | dot_draw (void *progress) |
410 | 0 | { |
411 | 0 | struct dot_progress *dp = progress; |
412 | 0 | int dot_bytes = opt.dot_bytes; |
413 | 0 | wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; |
414 | |
|
415 | 0 | log_set_flush (false); |
416 | |
|
417 | 0 | while (dp->accumulated >= dot_bytes) |
418 | 0 | { |
419 | 0 | dp->accumulated -= dot_bytes; |
420 | 0 | if (dp->dots == 0) |
421 | 0 | logprintf (LOG_PROGRESS, "\n%6sK", |
422 | 0 | number_to_static_string (dp->rows * ROW_BYTES / 1024)); |
423 | |
|
424 | 0 | if (dp->dots % opt.dot_spacing == 0) |
425 | 0 | logputs (LOG_PROGRESS, " "); |
426 | 0 | logputs (LOG_PROGRESS, "."); |
427 | |
|
428 | 0 | ++dp->dots; |
429 | 0 | if (dp->dots >= opt.dots_in_line) |
430 | 0 | { |
431 | 0 | dp->dots = 0; |
432 | 0 | #if ADD_DOT_ROWS_THRS |
433 | 0 | { |
434 | 0 | wgint added_rows = 1; |
435 | 0 | if (dp->accumulated >= (ROW_BYTES << ADD_DOT_ROWS_THRS)) |
436 | 0 | { |
437 | 0 | added_rows += dp->accumulated / ROW_BYTES; |
438 | 0 | dp->accumulated %= ROW_BYTES; |
439 | 0 | } |
440 | 0 | if (WGINT_MAX - dp->rows >= added_rows) |
441 | 0 | dp->rows += added_rows; |
442 | 0 | else |
443 | 0 | dp->rows = WGINT_MAX; |
444 | 0 | print_row_stats (dp, dp->dltime, false, added_rows); |
445 | 0 | } |
446 | | #else |
447 | | if (dp->rows < WGINT_MAX) |
448 | | ++dp->rows; |
449 | | print_row_stats (dp, dp->dltime, false); |
450 | | #endif /* ADD_DOT_ROWS_THRS */ |
451 | 0 | } |
452 | 0 | } |
453 | |
|
454 | 0 | log_set_flush (true); |
455 | 0 | } |
456 | | |
457 | | /* Dot-progress backend for progress_finish. */ |
458 | | |
459 | | static void |
460 | | dot_finish (void *progress, double dltime) |
461 | 0 | { |
462 | 0 | struct dot_progress *dp = progress; |
463 | 0 | wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; |
464 | 0 | int i; |
465 | |
|
466 | 0 | log_set_flush (false); |
467 | |
|
468 | 0 | if (dp->dots == 0) |
469 | 0 | logprintf (LOG_PROGRESS, "\n%6sK", |
470 | 0 | number_to_static_string (dp->rows * ROW_BYTES / 1024)); |
471 | 0 | for (i = dp->dots; i < opt.dots_in_line; i++) |
472 | 0 | { |
473 | 0 | if (i % opt.dot_spacing == 0) |
474 | 0 | logputs (LOG_PROGRESS, " "); |
475 | 0 | logputs (LOG_PROGRESS, " "); |
476 | 0 | } |
477 | | |
478 | | // sanitize input |
479 | 0 | if (dltime >= INT_MAX) |
480 | 0 | dltime = INT_MAX - 1; |
481 | 0 | else if (dltime < 0) |
482 | 0 | dltime = 0; |
483 | 0 | #if ADD_DOT_ROWS_THRS |
484 | 0 | print_row_stats (dp, dltime, true, 1); |
485 | | #else |
486 | | print_row_stats (dp, dltime, true); |
487 | | #endif |
488 | 0 | logputs (LOG_PROGRESS, "\n\n"); |
489 | 0 | log_set_flush (false); |
490 | |
|
491 | 0 | xfree (dp); |
492 | 0 | } |
493 | | |
494 | | /* This function interprets the progress "parameters". For example, |
495 | | if Wget is invoked with --progress=dot:mega, it will set the |
496 | | "dot-style" to "mega". Valid styles are default, binary, mega, and |
497 | | giga. */ |
498 | | |
499 | | static void |
500 | | dot_set_params (const char *params) |
501 | 0 | { |
502 | 0 | current_impl->interactive = false; |
503 | 0 | if (!params || !*params) |
504 | 0 | params = opt.dot_style; |
505 | |
|
506 | 0 | if (!params) |
507 | 0 | return; |
508 | | |
509 | | /* We use this to set the retrieval style. */ |
510 | 0 | if (!c_strcasecmp (params, "default")) |
511 | 0 | { |
512 | | /* Default style: 1K dots, 10 dots in a cluster, 50 dots in a |
513 | | line. */ |
514 | 0 | opt.dot_bytes = 1024; |
515 | 0 | opt.dot_spacing = 10; |
516 | 0 | opt.dots_in_line = 50; |
517 | 0 | } |
518 | 0 | else if (!c_strcasecmp (params, "binary")) |
519 | 0 | { |
520 | | /* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots |
521 | | (384K) in a line. */ |
522 | 0 | opt.dot_bytes = 8192; |
523 | 0 | opt.dot_spacing = 16; |
524 | 0 | opt.dots_in_line = 48; |
525 | 0 | } |
526 | 0 | else if (!c_strcasecmp (params, "mega")) |
527 | 0 | { |
528 | | /* "Mega" retrieval, for retrieving very long files; each dot is |
529 | | 64K, 8 dots in a cluster, 6 clusters (3M) in a line. */ |
530 | 0 | opt.dot_bytes = 65536L; |
531 | 0 | opt.dot_spacing = 8; |
532 | 0 | opt.dots_in_line = 48; |
533 | 0 | } |
534 | 0 | else if (!c_strcasecmp (params, "giga")) |
535 | 0 | { |
536 | | /* "Giga" retrieval, for retrieving very very *very* long files; |
537 | | each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a |
538 | | line. */ |
539 | 0 | opt.dot_bytes = (1L << 20); |
540 | 0 | opt.dot_spacing = 8; |
541 | 0 | opt.dots_in_line = 32; |
542 | 0 | } |
543 | 0 | else |
544 | 0 | fprintf (stderr, |
545 | 0 | _("Invalid dot style specification %s; leaving unchanged.\n"), |
546 | 0 | quote (params)); |
547 | 0 | } |
548 | | |
549 | | /* "Thermometer" (bar) progress. */ |
550 | | |
551 | | /* Assumed screen width if we can't find the real value. */ |
552 | 0 | #define DEFAULT_SCREEN_WIDTH 80 |
553 | | |
554 | | /* Minimum screen width we'll try to work with. If this is too small, |
555 | | create_image will overflow the buffer. |
556 | | width = width/4 + 1 + PROGRESS_PERCENT_LEN + PROGRESS_FILESIZE_LEN |
557 | | + PROGRESS_DWNLOAD_RATE + PROGRESS_ETA_LEN => widh = 38*4/3 (+1) */ |
558 | 0 | #define MINIMUM_SCREEN_WIDTH 51 |
559 | | |
560 | | /* The last known screen width. This can be updated by the code that |
561 | | detects that SIGWINCH was received (but it's never updated from the |
562 | | signal handler). */ |
563 | | static int screen_width; |
564 | | |
565 | | /* A flag that, when set, means SIGWINCH was received. */ |
566 | | static volatile sig_atomic_t received_sigwinch; |
567 | | |
568 | | /* Size of the download speed history ring. */ |
569 | 0 | #define DLSPEED_HISTORY_SIZE 20 |
570 | | |
571 | | /* The minimum time length of a history sample. By default, each |
572 | | sample is at least 150ms long, which means that, over the course of |
573 | | 20 samples, "current" download speed spans at least 3s into the |
574 | | past. */ |
575 | 0 | #define DLSPEED_SAMPLE_MIN 0.15 |
576 | | |
577 | | /* The time after which the download starts to be considered |
578 | | "stalled", i.e. the current bandwidth is not printed and the recent |
579 | | download speeds are scratched. */ |
580 | 0 | #define STALL_START_TIME 5 |
581 | | |
582 | | /* Time between screen refreshes will not be shorter than this, so |
583 | | that Wget doesn't swamp the TTY with output. */ |
584 | 0 | #define REFRESH_INTERVAL 0.2 |
585 | | |
586 | | /* Don't refresh the ETA too often to avoid jerkiness in predictions. |
587 | | This allows ETA to change approximately once per second. */ |
588 | 0 | #define ETA_REFRESH_INTERVAL 0.99 |
589 | | |
590 | | struct bar_progress { |
591 | | char *f_download; /* Filename of the downloaded file */ |
592 | | wgint initial_length; /* how many bytes have been downloaded |
593 | | previously. */ |
594 | | wgint total_length; /* expected total byte count when the |
595 | | download finishes */ |
596 | | wgint count; /* bytes downloaded so far */ |
597 | | |
598 | | double last_screen_update; /* time of the last screen update, |
599 | | measured since the beginning of |
600 | | download. */ |
601 | | |
602 | | double dltime; /* download time so far */ |
603 | | int width; /* screen width we're using at the |
604 | | time the progress gauge was |
605 | | created. this is different from |
606 | | the screen_width global variable in |
607 | | that the latter can be changed by a |
608 | | signal. */ |
609 | | char *buffer; /* buffer where the bar "image" is |
610 | | stored. */ |
611 | | int tick; /* counter used for drawing the |
612 | | progress bar where the total size |
613 | | is not known. */ |
614 | | |
615 | | /* The following variables (kept in a struct for namespace reasons) |
616 | | keep track of recent download speeds. See bar_update() for |
617 | | details. */ |
618 | | struct bar_progress_hist { |
619 | | int pos; |
620 | | double times[DLSPEED_HISTORY_SIZE]; |
621 | | wgint bytes[DLSPEED_HISTORY_SIZE]; |
622 | | |
623 | | /* The sum of times and bytes respectively, maintained for |
624 | | efficiency. */ |
625 | | double total_time; |
626 | | wgint total_bytes; |
627 | | } hist; |
628 | | |
629 | | double recent_start; /* timestamp of beginning of current |
630 | | position. */ |
631 | | wgint recent_bytes; /* bytes downloaded so far. */ |
632 | | |
633 | | bool stalled; /* set when no data arrives for longer |
634 | | than STALL_START_TIME, then reset |
635 | | when new data arrives. */ |
636 | | |
637 | | /* create_image() uses these to make sure that ETA information |
638 | | doesn't flicker. */ |
639 | | double last_eta_time; /* time of the last update to download |
640 | | speed and ETA, measured since the |
641 | | beginning of download. */ |
642 | | int last_eta_value; |
643 | | }; |
644 | | |
645 | | static void create_image (struct bar_progress *, double, bool); |
646 | | static void display_image (char *); |
647 | | |
648 | | #if USE_NLS_PROGRESS_BAR |
649 | | static size_t |
650 | | prepare_filename (char *dest, const char *src) |
651 | 0 | { |
652 | 0 | size_t ret = 1; |
653 | 0 | if (src) |
654 | 0 | { |
655 | 0 | mbi_iterator_t iter; |
656 | 0 | mbchar_t mbc; |
657 | 0 | mbi_init (iter, src, strlen (src)); |
658 | 0 | while (mbi_avail (iter)) |
659 | 0 | { |
660 | 0 | size_t i; |
661 | 0 | mbc = mbi_cur(iter); |
662 | | /* replace invalid || unprintable || zero-width mbc ws hexdgt code */ |
663 | 0 | if (!mb_isprint (mbc) || !mb_width (mbc)) |
664 | 0 | for (i=0; i < mb_len (mbc); i++) |
665 | 0 | { |
666 | 0 | if (dest) |
667 | 0 | dest += sprintf (dest, "%%%02x", (unsigned char) *(mb_ptr(mbc) + i)); |
668 | 0 | ret += 3; |
669 | 0 | } |
670 | 0 | else |
671 | 0 | { |
672 | 0 | if (dest) |
673 | 0 | for (i=0; i < mb_len (mbc); i++) |
674 | 0 | *dest++ = *(mb_ptr (mbc) + i); |
675 | 0 | ret += mb_len (mbc); |
676 | 0 | } |
677 | 0 | mbi_advance (iter); |
678 | 0 | } |
679 | 0 | } |
680 | 0 | if (dest) |
681 | 0 | *dest = 0; |
682 | 0 | return ret; |
683 | 0 | } |
684 | | #else |
685 | | #include <ctype.h> |
686 | | static size_t |
687 | | prepare_filename (char *dest, const char *src) |
688 | | { |
689 | | size_t ret = 1; |
690 | | if (src) |
691 | | while (*src) |
692 | | { |
693 | | /* isprint with some lang return false for some chars */ |
694 | | if(!iscntrl (*src)) |
695 | | { |
696 | | if (dest) |
697 | | *dest++ = *src; |
698 | | ret++; |
699 | | } |
700 | | else |
701 | | { |
702 | | if (dest) |
703 | | dest += sprintf (dest, "%%%02x", (unsigned char) *src ); |
704 | | ret += 3; |
705 | | } |
706 | | src++; |
707 | | } |
708 | | if (dest) |
709 | | *dest = 0; |
710 | | return ret; |
711 | | } |
712 | | #endif /* USE_NLS_PROGRESS_BAR */ |
713 | | |
714 | | static void * |
715 | | bar_create (const char *f_download, wgint initial, wgint total) |
716 | 0 | { |
717 | 0 | struct bar_progress *bp = xnew0 (struct bar_progress); |
718 | | |
719 | | /* In theory, our callers should take care of this pathological |
720 | | case, but it can sometimes happen. */ |
721 | 0 | if (initial > total) |
722 | 0 | total = initial; |
723 | |
|
724 | 0 | bp->initial_length = initial; |
725 | 0 | bp->total_length = total; |
726 | | /* Zero-width mbc must be replaced to avoid buffer overflow. |
727 | | Another way is to allocate a buffer that allows contain |
728 | | full f_download, but in this case some escape sequences may break "bar" */ |
729 | 0 | bp->f_download = xmalloc (prepare_filename (NULL, f_download)); |
730 | 0 | prepare_filename (bp->f_download, f_download); |
731 | | |
732 | | /* Initialize screen_width if this hasn't been done or if it might |
733 | | have changed, as indicated by receiving SIGWINCH. */ |
734 | 0 | if (!screen_width || received_sigwinch) |
735 | 0 | { |
736 | 0 | screen_width = determine_screen_width (); |
737 | 0 | if (!screen_width) |
738 | 0 | screen_width = DEFAULT_SCREEN_WIDTH; |
739 | 0 | else if (screen_width < MINIMUM_SCREEN_WIDTH) |
740 | 0 | screen_width = MINIMUM_SCREEN_WIDTH; |
741 | 0 | received_sigwinch = 0; |
742 | 0 | } |
743 | | |
744 | | /* - 1 because we don't want to use the last screen column. */ |
745 | 0 | bp->width = screen_width - 1; |
746 | | /* + enough space for the terminating zero, and hopefully enough room |
747 | | * for multibyte characters. */ |
748 | 0 | #define BUF_LEN (bp->width * 2 + 100) |
749 | 0 | bp->buffer = xcalloc (BUF_LEN, 1); |
750 | |
|
751 | 0 | logputs (LOG_VERBOSE, "\n"); |
752 | |
|
753 | 0 | create_image (bp, 0, false); |
754 | 0 | display_image (bp->buffer); |
755 | |
|
756 | 0 | return bp; |
757 | 0 | } |
758 | | |
759 | | static void update_speed_ring (struct bar_progress *, wgint, double); |
760 | | |
761 | | static void |
762 | | bar_update (void *progress, wgint howmuch, double dltime) |
763 | 0 | { |
764 | 0 | struct bar_progress *bp = progress; |
765 | |
|
766 | 0 | bp->dltime = dltime; |
767 | 0 | if (WGINT_MAX - (bp->count + bp->initial_length) >= howmuch) |
768 | 0 | bp->count += howmuch; |
769 | 0 | else |
770 | 0 | bp->count = WGINT_MAX - bp->initial_length; |
771 | 0 | if (bp->total_length > 0 |
772 | 0 | && bp->count + bp->initial_length > bp->total_length) |
773 | | /* We could be downloading more than total_length, e.g. when the |
774 | | server sends an incorrect Content-Length header. In that case, |
775 | | adjust bp->total_length to the new reality, so that the code in |
776 | | create_image() that depends on total size being smaller or |
777 | | equal to the expected size doesn't abort. */ |
778 | 0 | bp->total_length = bp->initial_length + bp->count; |
779 | |
|
780 | 0 | update_speed_ring (bp, howmuch, dltime); |
781 | 0 | } |
782 | | |
783 | | static void |
784 | | bar_draw (void *progress) |
785 | 0 | { |
786 | 0 | bool force_screen_update = false; |
787 | 0 | struct bar_progress *bp = progress; |
788 | | |
789 | | /* If SIGWINCH (the window size change signal) been received, |
790 | | determine the new screen size and update the screen. */ |
791 | 0 | if (received_sigwinch) |
792 | 0 | { |
793 | 0 | int old_width = screen_width; |
794 | 0 | screen_width = determine_screen_width (); |
795 | 0 | if (!screen_width) |
796 | 0 | screen_width = DEFAULT_SCREEN_WIDTH; |
797 | 0 | else if (screen_width < MINIMUM_SCREEN_WIDTH) |
798 | 0 | screen_width = MINIMUM_SCREEN_WIDTH; |
799 | 0 | if (screen_width != old_width) |
800 | 0 | { |
801 | 0 | bp->width = screen_width - 1; |
802 | 0 | bp->buffer = xrealloc (bp->buffer, BUF_LEN); |
803 | 0 | force_screen_update = true; |
804 | 0 | } |
805 | 0 | received_sigwinch = 0; |
806 | 0 | } |
807 | |
|
808 | 0 | if (bp->dltime - bp->last_screen_update < REFRESH_INTERVAL && !force_screen_update) |
809 | | /* Don't update more often than five times per second. */ |
810 | 0 | return; |
811 | | |
812 | 0 | create_image (bp, bp->dltime, false); |
813 | 0 | display_image (bp->buffer); |
814 | 0 | bp->last_screen_update = bp->dltime; |
815 | 0 | } |
816 | | |
817 | | static void |
818 | | bar_finish (void *progress, double dltime) |
819 | 0 | { |
820 | 0 | struct bar_progress *bp = progress; |
821 | |
|
822 | 0 | if (bp->total_length > 0 |
823 | 0 | && bp->count + bp->initial_length > bp->total_length) |
824 | | /* See bar_update() for explanation. */ |
825 | 0 | bp->total_length = bp->initial_length + bp->count; |
826 | |
|
827 | 0 | create_image (bp, dltime, true); |
828 | 0 | display_image (bp->buffer); |
829 | |
|
830 | 0 | logputs (LOG_VERBOSE, "\n"); |
831 | 0 | logputs (LOG_PROGRESS, "\n"); |
832 | |
|
833 | 0 | xfree (bp->f_download); |
834 | 0 | xfree (bp->buffer); |
835 | 0 | xfree (bp); |
836 | 0 | } |
837 | | |
838 | | /* This code attempts to maintain the notion of a "current" download |
839 | | speed, over the course of no less than 3s. (Shorter intervals |
840 | | produce very erratic results.) |
841 | | |
842 | | To do so, it samples the speed in 150ms intervals and stores the |
843 | | recorded samples in a FIFO history ring. The ring stores no more |
844 | | than 20 intervals, hence the history covers the period of at least |
845 | | three seconds and at most 20 reads into the past. This method |
846 | | should produce reasonable results for downloads ranging from very |
847 | | slow to very fast. |
848 | | |
849 | | The idea is that for fast downloads, we get the speed over exactly |
850 | | the last three seconds. For slow downloads (where a network read |
851 | | takes more than 150ms to complete), we get the speed over a larger |
852 | | time period, as large as it takes to complete twenty reads. This |
853 | | is good because slow downloads tend to fluctuate more and a |
854 | | 3-second average would be too erratic. */ |
855 | | |
856 | | static void |
857 | | update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime) |
858 | 0 | { |
859 | 0 | struct bar_progress_hist *hist = &bp->hist; |
860 | 0 | double recent_age = dltime - bp->recent_start; |
861 | | |
862 | | /* Update the download count. */ |
863 | 0 | bp->recent_bytes += howmuch; |
864 | | |
865 | | /* For very small time intervals, we return after having updated the |
866 | | "recent" download count. When its age reaches or exceeds minimum |
867 | | sample time, it will be recorded in the history ring. */ |
868 | 0 | if (recent_age < DLSPEED_SAMPLE_MIN) |
869 | 0 | return; |
870 | | |
871 | 0 | if (howmuch == 0) |
872 | 0 | { |
873 | | /* If we're not downloading anything, we might be stalling, |
874 | | i.e. not downloading anything for an extended period of time. |
875 | | Since 0-reads do not enter the history ring, recent_age |
876 | | effectively measures the time since last read. */ |
877 | 0 | if (recent_age >= STALL_START_TIME) |
878 | 0 | { |
879 | | /* If we're stalling, reset the ring contents because it's |
880 | | stale and because it will make bar_update stop printing |
881 | | the (bogus) current bandwidth. */ |
882 | 0 | bp->stalled = true; |
883 | 0 | xzero (*hist); |
884 | 0 | bp->recent_bytes = 0; |
885 | 0 | } |
886 | 0 | return; |
887 | 0 | } |
888 | | |
889 | | /* We now have a non-zero amount of to store to the speed ring. */ |
890 | | |
891 | | /* If the stall status was acquired, reset it. */ |
892 | 0 | if (bp->stalled) |
893 | 0 | { |
894 | 0 | bp->stalled = false; |
895 | | /* "recent_age" includes the entire stalled period, which |
896 | | could be very long. Don't update the speed ring with that |
897 | | value because the current bandwidth would start too small. |
898 | | Start with an arbitrary (but more reasonable) time value and |
899 | | let it level out. */ |
900 | 0 | recent_age = 1; |
901 | 0 | } |
902 | | |
903 | | /* Store "recent" bytes and download time to history ring at the |
904 | | position POS. */ |
905 | | |
906 | | /* To correctly maintain the totals, first invalidate existing data |
907 | | (least recent in time) at this position. */ |
908 | 0 | hist->total_time -= hist->times[hist->pos]; |
909 | 0 | hist->total_bytes -= hist->bytes[hist->pos]; |
910 | | |
911 | | /* Now store the new data and update the totals. */ |
912 | 0 | hist->times[hist->pos] = recent_age; |
913 | 0 | hist->bytes[hist->pos] = bp->recent_bytes; |
914 | 0 | hist->total_time += recent_age; |
915 | 0 | hist->total_bytes += bp->recent_bytes; |
916 | | |
917 | | /* Start a new "recent" period. */ |
918 | 0 | bp->recent_start = dltime; |
919 | 0 | bp->recent_bytes = 0; |
920 | | |
921 | | /* Advance the current ring position. */ |
922 | 0 | if (++hist->pos == DLSPEED_HISTORY_SIZE) |
923 | 0 | hist->pos = 0; |
924 | |
|
925 | | #if 0 |
926 | | /* Sledgehammer check to verify that the totals are accurate. */ |
927 | | { |
928 | | int i; |
929 | | double sumt = 0, sumb = 0; |
930 | | for (i = 0; i < DLSPEED_HISTORY_SIZE; i++) |
931 | | { |
932 | | sumt += hist->times[i]; |
933 | | sumb += hist->bytes[i]; |
934 | | } |
935 | | assert (sumb == hist->total_bytes); |
936 | | /* We can't use assert(sumt==hist->total_time) because some |
937 | | precision is lost by adding and subtracting floating-point |
938 | | numbers. But during a download this precision should not be |
939 | | detectable, i.e. no larger than 1ns. */ |
940 | | double diff = sumt - hist->total_time; |
941 | | if (diff < 0) diff = -diff; |
942 | | assert (diff < 1e-9); |
943 | | } |
944 | | #endif |
945 | 0 | } |
946 | | |
947 | | #if USE_NLS_PROGRESS_BAR |
948 | | static int |
949 | | count_cols (const char *mbs) |
950 | 0 | { |
951 | 0 | mbchar_t mbc; |
952 | 0 | mbi_iterator_t iter; |
953 | 0 | int cols = 0; |
954 | 0 | mbi_init (iter, mbs, strlen(mbs)); |
955 | 0 | while (mbi_avail (iter)) |
956 | 0 | { |
957 | 0 | mbc = mbi_cur (iter); |
958 | 0 | cols += mb_width (mbc); |
959 | 0 | mbi_advance (iter); |
960 | 0 | } |
961 | 0 | return cols; |
962 | 0 | } |
963 | | |
964 | | static int |
965 | | cols_to_bytes (const char *mbs, const int cols, int *ncols) |
966 | 0 | { |
967 | 0 | int p_cols = 0, bytes = 0; |
968 | 0 | mbchar_t mbc; |
969 | 0 | mbi_iterator_t iter; |
970 | 0 | mbi_init (iter, mbs, strlen(mbs)); |
971 | 0 | while (p_cols < cols && mbi_avail (iter)) |
972 | 0 | { |
973 | 0 | mbc = mbi_cur (iter); |
974 | 0 | p_cols += mb_width (mbc); |
975 | | /* The multibyte character has exceeded the total number of columns we |
976 | | * have available. The remaining bytes will be padded with a space. */ |
977 | 0 | if (p_cols > cols) |
978 | 0 | { |
979 | 0 | p_cols -= mb_width (mbc); |
980 | 0 | break; |
981 | 0 | } |
982 | 0 | bytes += mb_len (mbc); |
983 | 0 | mbi_advance (iter); |
984 | 0 | } |
985 | 0 | *ncols = p_cols; |
986 | 0 | return bytes; |
987 | 0 | } |
988 | | #else |
989 | | static int count_cols (const char *mbs) { return (int) strlen(mbs); } |
990 | | |
991 | | static int |
992 | | cols_to_bytes (const char *mbs, const int cols, int *ncols) |
993 | | { |
994 | | int len = strlen(mbs); |
995 | | int ret = len < cols ? len : cols; |
996 | | *ncols = ret; |
997 | | return ret; |
998 | | } |
999 | | #endif |
1000 | | |
1001 | | static const char * |
1002 | | get_eta (int *bcd) |
1003 | 0 | { |
1004 | | /* TRANSLATORS: "ETA" is English-centric, but this must |
1005 | | be short, ideally 3 chars. Abbreviate if necessary. */ |
1006 | 0 | static const char eta_str[] = N_(" eta %s"); |
1007 | 0 | static const char *eta_trans; |
1008 | 0 | static int bytes_cols_diff; |
1009 | 0 | if (eta_trans == NULL) |
1010 | 0 | { |
1011 | 0 | int nbytes; |
1012 | 0 | int ncols; |
1013 | |
|
1014 | 0 | #if USE_NLS_PROGRESS_BAR |
1015 | 0 | eta_trans = _(eta_str); |
1016 | | #else |
1017 | | eta_trans = eta_str; |
1018 | | #endif |
1019 | | |
1020 | | /* Determine the number of bytes used in the translated string, |
1021 | | * versus the number of columns used. This is to figure out how |
1022 | | * many spaces to add at the end to pad to the full line width. |
1023 | | * |
1024 | | * We'll store the difference between the number of bytes and |
1025 | | * number of columns, so that removing this from the string length |
1026 | | * will reveal the total number of columns in the progress bar. */ |
1027 | 0 | nbytes = strlen (eta_trans); |
1028 | 0 | ncols = count_cols (eta_trans); |
1029 | 0 | bytes_cols_diff = nbytes - ncols; |
1030 | 0 | } |
1031 | |
|
1032 | 0 | if (bcd != NULL) |
1033 | 0 | *bcd = bytes_cols_diff; |
1034 | |
|
1035 | 0 | return eta_trans; |
1036 | 0 | } |
1037 | | |
1038 | 0 | #define APPEND_LITERAL(s) do { \ |
1039 | 0 | memcpy (p, s, sizeof (s) - 1); \ |
1040 | 0 | p += sizeof (s) - 1; \ |
1041 | 0 | } while (0) |
1042 | | |
1043 | | static void |
1044 | | create_image (struct bar_progress *bp, double dl_total_time, bool done) |
1045 | 0 | { |
1046 | 0 | const int MAX_FILENAME_COLS = bp->width / 4; |
1047 | 0 | char *p = bp->buffer; |
1048 | 0 | wgint size = bp->initial_length + bp->count; |
1049 | |
|
1050 | 0 | struct bar_progress_hist *hist = &bp->hist; |
1051 | 0 | int orig_filename_cols = count_cols (bp->f_download); |
1052 | |
|
1053 | 0 | int padding; |
1054 | | |
1055 | | /* The progress bar should look like this: |
1056 | | file xx% [=======> ] nnn.nnK 12.34KB/s eta 36m 51s |
1057 | | |
1058 | | Calculate the geometry. The idea is to assign as much room as |
1059 | | possible to the progress bar. The other idea is to never let |
1060 | | things "jitter", i.e. pad elements that vary in size so that |
1061 | | their variance does not affect the placement of other elements. |
1062 | | It would be especially bad for the progress bar to be resized |
1063 | | randomly. |
1064 | | |
1065 | | "file " - Downloaded filename - MAX_FILENAME_COLS chars + 1 |
1066 | | "xx% " or "100%" - percentage - 4 chars |
1067 | | "[]" - progress bar decorations - 2 chars |
1068 | | " nnn.nnK" - downloaded bytes - 7 chars + 1 |
1069 | | " 12.5KB/s" - download rate - 8 chars + 1 |
1070 | | " eta 36m 51s" - ETA - 14 chars |
1071 | | |
1072 | | "=====>..." - progress bar - the rest |
1073 | | */ |
1074 | | |
1075 | | /* TODO: Ask the Turkish Translators to fix their translation for the "done" |
1076 | | * mode of progress bar. Use one less character. Once that is done, redice |
1077 | | * PROGRESS_ETA_LEN by 1. |
1078 | | */ |
1079 | 0 | #define PROGRESS_FILENAME_LEN MAX_FILENAME_COLS + 1 |
1080 | 0 | #define PROGRESS_PERCENT_LEN 4 |
1081 | 0 | #define PROGRESS_DECORAT_LEN 2 |
1082 | 0 | #define PROGRESS_FILESIZE_LEN 7 + 1 |
1083 | 0 | #define PROGRESS_DWNLOAD_RATE 8 + 2 |
1084 | 0 | #define PROGRESS_ETA_LEN 15 |
1085 | |
|
1086 | 0 | int progress_size = bp->width - (PROGRESS_FILENAME_LEN + PROGRESS_PERCENT_LEN + |
1087 | 0 | PROGRESS_DECORAT_LEN + PROGRESS_FILESIZE_LEN + |
1088 | 0 | PROGRESS_DWNLOAD_RATE + PROGRESS_ETA_LEN); |
1089 | | |
1090 | | /* The difference between the number of bytes used, |
1091 | | and the number of columns used. */ |
1092 | 0 | int bytes_cols_diff = 0; |
1093 | 0 | int cols_diff; |
1094 | 0 | const char *down_size; |
1095 | |
|
1096 | 0 | if (progress_size < 5) |
1097 | 0 | progress_size = 0; |
1098 | | |
1099 | | // sanitize input |
1100 | 0 | if (dl_total_time >= INT_MAX) |
1101 | 0 | dl_total_time = INT_MAX - 1; |
1102 | 0 | else if (dl_total_time < 0) |
1103 | 0 | dl_total_time = 0; |
1104 | |
|
1105 | 0 | if (orig_filename_cols < MAX_FILENAME_COLS) |
1106 | 0 | { |
1107 | 0 | p += sprintf (p, "%s", bp->f_download); |
1108 | 0 | padding = MAX_FILENAME_COLS - orig_filename_cols + 1; |
1109 | 0 | memset (p, ' ', padding); |
1110 | 0 | p += padding; |
1111 | 0 | } |
1112 | 0 | else |
1113 | 0 | { |
1114 | | /* |
1115 | | memcpy(p, bp->f_download, MAX_FILENAME_COLS); |
1116 | | p += MAX_FILENAME_COLS; |
1117 | | *p++ = ' '; |
1118 | | } |
1119 | | */ |
1120 | 0 | int offset_cols; |
1121 | 0 | int bytes_in_filename, offset_bytes, col; |
1122 | 0 | int *cols_ret = &col; |
1123 | |
|
1124 | 0 | #define MIN_SCROLL_TEXT 5 |
1125 | 0 | if ((orig_filename_cols > MAX_FILENAME_COLS + MIN_SCROLL_TEXT) && |
1126 | 0 | !opt.noscroll && |
1127 | 0 | !done) |
1128 | 0 | { |
1129 | 0 | offset_cols = ((int) bp->tick + orig_filename_cols + MAX_FILENAME_COLS / 2) |
1130 | 0 | % (orig_filename_cols + MAX_FILENAME_COLS); |
1131 | 0 | if (offset_cols > orig_filename_cols) |
1132 | 0 | { |
1133 | 0 | padding = MAX_FILENAME_COLS - (offset_cols - orig_filename_cols); |
1134 | 0 | memset(p, ' ', padding); |
1135 | 0 | p += padding; |
1136 | 0 | offset_cols = 0; |
1137 | 0 | } |
1138 | 0 | else |
1139 | 0 | padding = 0; |
1140 | 0 | } |
1141 | 0 | else |
1142 | 0 | { |
1143 | 0 | padding = 0; |
1144 | 0 | offset_cols = 0; |
1145 | 0 | } |
1146 | 0 | offset_bytes = cols_to_bytes (bp->f_download, offset_cols, cols_ret); |
1147 | 0 | bytes_in_filename = cols_to_bytes (bp->f_download + offset_bytes, |
1148 | 0 | MAX_FILENAME_COLS - padding, |
1149 | 0 | cols_ret); |
1150 | 0 | memcpy (p, bp->f_download + offset_bytes, bytes_in_filename); |
1151 | 0 | p += bytes_in_filename; |
1152 | 0 | padding = MAX_FILENAME_COLS - (padding + *cols_ret); |
1153 | 0 | memset (p, ' ', padding + 1); |
1154 | 0 | p += padding + 1; |
1155 | 0 | } |
1156 | | |
1157 | | |
1158 | | /* "xx% " */ |
1159 | 0 | if (bp->total_length > 0) |
1160 | 0 | { |
1161 | 0 | int percentage = 100.0 * size / bp->total_length; |
1162 | 0 | assert (percentage <= 100); |
1163 | 0 | p += sprintf (p, "%3d%%", percentage); |
1164 | 0 | } |
1165 | 0 | else |
1166 | 0 | { |
1167 | 0 | memset (p, ' ', PROGRESS_PERCENT_LEN); |
1168 | 0 | p += PROGRESS_PERCENT_LEN; |
1169 | 0 | } |
1170 | | |
1171 | | /* The progress bar: "[====> ]" or "[++==> ]". */ |
1172 | 0 | if (progress_size && bp->total_length > 0) |
1173 | 0 | { |
1174 | | /* Size of the initial portion. */ |
1175 | 0 | int insz = (double)bp->initial_length / bp->total_length * progress_size; |
1176 | | |
1177 | | /* Size of the downloaded portion. */ |
1178 | 0 | int dlsz = (double)size / bp->total_length * progress_size; |
1179 | |
|
1180 | 0 | char *begin; |
1181 | |
|
1182 | 0 | assert (dlsz <= progress_size); |
1183 | 0 | assert (insz <= dlsz); |
1184 | |
|
1185 | 0 | *p++ = '['; |
1186 | 0 | begin = p; |
1187 | | |
1188 | | /* Print the initial portion of the download with '+' chars, the |
1189 | | rest with '=' and one '>'. */ |
1190 | 0 | memset (p, '+', insz); |
1191 | 0 | p += insz; |
1192 | |
|
1193 | 0 | dlsz -= insz; |
1194 | 0 | if (dlsz > 0) |
1195 | 0 | { |
1196 | 0 | memset (p, '=', dlsz-1); |
1197 | 0 | p += dlsz - 1; |
1198 | 0 | *p++ = '>'; |
1199 | 0 | } |
1200 | |
|
1201 | 0 | memset (p, ' ', (progress_size - (p - begin))); |
1202 | 0 | p += (progress_size - (p - begin)); |
1203 | 0 | *p++ = ']'; |
1204 | 0 | } |
1205 | 0 | else if (progress_size) |
1206 | 0 | { |
1207 | | /* If we can't draw a real progress bar, then at least show |
1208 | | *something* to the user. */ |
1209 | 0 | int ind = bp->tick % (progress_size * 2 - 6); |
1210 | 0 | int i, pos; |
1211 | | |
1212 | | /* Make the star move in two directions. */ |
1213 | 0 | if (ind < progress_size - 2) |
1214 | 0 | pos = ind + 1; |
1215 | 0 | else |
1216 | 0 | pos = progress_size - (ind - progress_size + 5); |
1217 | |
|
1218 | 0 | *p++ = '['; |
1219 | 0 | for (i = 0; i < progress_size; i++) |
1220 | 0 | { |
1221 | 0 | if (i == pos - 1) *p++ = '<'; |
1222 | 0 | else if (i == pos ) *p++ = '='; |
1223 | 0 | else if (i == pos + 1) *p++ = '>'; |
1224 | 0 | else |
1225 | 0 | *p++ = ' '; |
1226 | 0 | } |
1227 | 0 | *p++ = ']'; |
1228 | |
|
1229 | 0 | } |
1230 | 0 | ++bp->tick; |
1231 | | |
1232 | | /* " 234.56M" */ |
1233 | 0 | down_size = human_readable (size, 1000, 2); |
1234 | 0 | cols_diff = PROGRESS_FILESIZE_LEN - count_cols (down_size); |
1235 | 0 | if (cols_diff > 0) |
1236 | 0 | { |
1237 | 0 | memset (p, ' ', cols_diff); |
1238 | 0 | p += cols_diff; |
1239 | 0 | } |
1240 | 0 | p += sprintf (p, "%s", down_size); |
1241 | | |
1242 | | /* " 12.52Kb/s or 12.52KB/s" */ |
1243 | 0 | if (hist->total_time > 0 && hist->total_bytes) |
1244 | 0 | { |
1245 | 0 | static const char *short_units[] = { " B/s", "KB/s", "MB/s", "GB/s", "TB/s" }; |
1246 | 0 | static const char *short_units_bits[] = { " b/s", "Kb/s", "Mb/s", "Gb/s", "Tb/s" }; |
1247 | 0 | int units = 0; |
1248 | | /* Calculate the download speed using the history ring and |
1249 | | recent data that hasn't made it to the ring yet. */ |
1250 | 0 | wgint dlquant = hist->total_bytes + bp->recent_bytes; |
1251 | 0 | double dltime = hist->total_time + (dl_total_time - bp->recent_start); |
1252 | 0 | double dlspeed = calc_rate (dlquant, dltime, &units); |
1253 | 0 | p += sprintf (p, " %4.*f%s", dlspeed >= 99.95 ? 0 : dlspeed >= 9.995 ? 1 : 2, |
1254 | 0 | dlspeed, !opt.report_bps ? short_units[units] : short_units_bits[units]); |
1255 | 0 | } |
1256 | 0 | else |
1257 | 0 | APPEND_LITERAL (" --.-KB/s"); |
1258 | |
|
1259 | 0 | if (!done) |
1260 | 0 | { |
1261 | | /* " eta ..m ..s"; wait for three seconds before displaying the ETA. |
1262 | | That's because the ETA value needs a while to become |
1263 | | reliable. */ |
1264 | 0 | if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3) |
1265 | 0 | { |
1266 | 0 | int eta; |
1267 | | |
1268 | | /* Don't change the value of ETA more than approximately once |
1269 | | per second; doing so would cause flashing without providing |
1270 | | any value to the user. */ |
1271 | 0 | if (bp->total_length != size |
1272 | 0 | && bp->last_eta_value != 0 |
1273 | 0 | && dl_total_time - bp->last_eta_time < ETA_REFRESH_INTERVAL) |
1274 | 0 | eta = bp->last_eta_value; |
1275 | 0 | else |
1276 | 0 | { |
1277 | | /* Calculate ETA using the average download speed to predict |
1278 | | the future speed. If you want to use a speed averaged |
1279 | | over a more recent period, replace dl_total_time with |
1280 | | hist->total_time and bp->count with hist->total_bytes. |
1281 | | I found that doing that results in a very jerky and |
1282 | | ultimately unreliable ETA. */ |
1283 | 0 | wgint bytes_remaining = bp->total_length - size; |
1284 | 0 | double eta_ = dl_total_time * bytes_remaining / bp->count; |
1285 | 0 | if (eta_ >= INT_MAX - 1) |
1286 | 0 | goto skip_eta; |
1287 | 0 | eta = (int) (eta_ + 0.5); |
1288 | 0 | bp->last_eta_value = eta; |
1289 | 0 | bp->last_eta_time = dl_total_time; |
1290 | 0 | } |
1291 | | |
1292 | 0 | p += sprintf (p, get_eta(&bytes_cols_diff), |
1293 | 0 | eta_to_human_short (eta, false)); |
1294 | 0 | } |
1295 | 0 | else if (bp->total_length > 0) |
1296 | 0 | { |
1297 | 0 | skip_eta: |
1298 | 0 | memset (p, ' ', PROGRESS_ETA_LEN); |
1299 | 0 | p += PROGRESS_ETA_LEN; |
1300 | 0 | } |
1301 | 0 | } |
1302 | 0 | else |
1303 | 0 | { |
1304 | | /* When the download is done, print the elapsed time. */ |
1305 | 0 | int nbytes; |
1306 | 0 | int ncols; |
1307 | | |
1308 | | /* TRANSLATORS: The meaning is "elapsed time", and it is shown |
1309 | | * next to the progress bar once the download is done. |
1310 | | * This should not take up more room than |
1311 | | * available here (6 columns). Abbreviate if necessary. */ |
1312 | 0 | strcpy (p, _(" in ")); |
1313 | 0 | nbytes = strlen (p); |
1314 | 0 | ncols = count_cols (p); |
1315 | 0 | bytes_cols_diff = nbytes - ncols; |
1316 | 0 | if (dl_total_time >= 10) |
1317 | 0 | ncols += sprintf (p + nbytes, "%s", eta_to_human_short ((int) (dl_total_time + 0.5), false)); |
1318 | 0 | else |
1319 | 0 | ncols += sprintf (p + nbytes, "%ss", print_decimal (dl_total_time)); |
1320 | 0 | p += ncols + bytes_cols_diff; |
1321 | 0 | if (ncols < PROGRESS_ETA_LEN) |
1322 | 0 | { |
1323 | 0 | memset (p, ' ', PROGRESS_ETA_LEN - ncols); |
1324 | 0 | p += PROGRESS_ETA_LEN - ncols; |
1325 | 0 | } |
1326 | 0 | } |
1327 | | |
1328 | 0 | *p = '\0'; |
1329 | |
|
1330 | 0 | padding = bp->width - count_cols (bp->buffer); |
1331 | 0 | assert (padding >= 0 && "Padding length became non-positive!"); |
1332 | 0 | if (padding > 0) |
1333 | 0 | { |
1334 | | // if (padding > BUF_LEN - (p - bp->buffer) - 1) |
1335 | | // padding = BUF_LEN - (p - bp->buffer) - 1; |
1336 | 0 | memset (p, ' ', padding); |
1337 | 0 | p += padding; |
1338 | 0 | *p = '\0'; |
1339 | 0 | } |
1340 | | |
1341 | | /* 2014-11-14 Darshit Shah <darnir@gmail.com> |
1342 | | * Assert that the length of the progress bar is lesser than the size of the |
1343 | | * screen with which we are dealing. This assertion *MUST* always be removed |
1344 | | * from the release code since we do not want Wget to crash and burn when the |
1345 | | * assertion fails. Instead Wget should continue downloading and display a |
1346 | | * horrible and irritating progress bar that spams the screen with newlines. |
1347 | | * |
1348 | | * By default, all assertions are disabled in a Wget build and are enabled |
1349 | | * only with the --enable-assert configure option. |
1350 | | */ |
1351 | 0 | assert (count_cols (bp->buffer) == bp->width); |
1352 | 0 | } |
1353 | | |
1354 | | /* Print the contents of the buffer as a one-line ASCII "image" so |
1355 | | that it can be overwritten next time. */ |
1356 | | |
1357 | | static void |
1358 | | display_image (char *buf) |
1359 | 0 | { |
1360 | 0 | bool old = log_set_save_context (false); |
1361 | 0 | logputs (LOG_PROGRESS, "\r"); |
1362 | 0 | logputs (LOG_PROGRESS, buf); |
1363 | 0 | log_set_save_context (old); |
1364 | 0 | } |
1365 | | |
1366 | | static void |
1367 | | bar_set_params (const char *params) |
1368 | 0 | { |
1369 | | /* if run_with_timeout() will be used for read, needs to disable interactive bar, |
1370 | | or on every timeout(1s) we will have 'retry' with error "decryption failed" */ |
1371 | | #if (defined(HAVE_LIBSSL) || defined(HAVE_LIBSSL32)) && defined(OPENSSL_RUN_WITHTIMEOUT) |
1372 | | current_impl->interactive = false; |
1373 | | #else |
1374 | 0 | current_impl->interactive = true; |
1375 | 0 | #endif |
1376 | 0 | if (params) |
1377 | 0 | { |
1378 | 0 | for (const char *param = params; *param; ) |
1379 | 0 | { |
1380 | 0 | if (!strncmp (param, "force", 5)) |
1381 | 0 | current_impl_locked = 1; |
1382 | 0 | else if (!strncmp (param, "noscroll", 8)) |
1383 | 0 | opt.noscroll = true; |
1384 | |
|
1385 | 0 | if (*(param = strchrnul(param, ':'))) |
1386 | 0 | param++; |
1387 | 0 | } |
1388 | 0 | } |
1389 | |
|
1390 | 0 | if (((opt.lfilename && opt.show_progress != 1) |
1391 | 0 | #ifdef HAVE_ISATTY |
1392 | | /* The progress bar doesn't make sense if the output is not a |
1393 | | TTY -- when logging to file, it is better to review the |
1394 | | dots. */ |
1395 | 0 | || !isatty (fileno (stderr)) |
1396 | 0 | #endif |
1397 | 0 | ) |
1398 | 0 | && !current_impl_locked) |
1399 | 0 | { |
1400 | | /* We're not printing to a TTY, so revert to the fallback |
1401 | | display. #### We're recursively calling |
1402 | | set_progress_implementation here, which is slightly kludgy. |
1403 | | It would be nicer if we provided that function a return value |
1404 | | indicating a failure of some sort. */ |
1405 | 0 | set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION); |
1406 | 0 | return; |
1407 | 0 | } |
1408 | 0 | } |
1409 | | |
1410 | | #ifdef SIGWINCH |
1411 | | void |
1412 | | progress_handle_sigwinch (int sig _GL_UNUSED) |
1413 | 0 | { |
1414 | 0 | received_sigwinch = 1; |
1415 | 0 | signal (SIGWINCH, progress_handle_sigwinch); |
1416 | 0 | } |
1417 | | #endif |
1418 | | |
1419 | | /* Provide a short human-readable rendition of the ETA. This is like |
1420 | | secs_to_human_time in main.c, except the output doesn't include |
1421 | | fractions (which would look silly in by nature imprecise ETA) and |
1422 | | takes less room. If the time is measured in hours, hours and |
1423 | | minutes (but not seconds) are shown; if measured in days, then days |
1424 | | and hours are shown. This ensures brevity while still displaying |
1425 | | as much as possible. |
1426 | | |
1427 | | If CONDENSED is true, the separator between minutes and seconds |
1428 | | (and hours and minutes, etc.) is not included, shortening the |
1429 | | display by one additional character. This is used for dot |
1430 | | progress. |
1431 | | |
1432 | | The display never occupies more than 7 characters of screen |
1433 | | space. */ |
1434 | | |
1435 | | static const char * |
1436 | | eta_to_human_short (int secs, bool condensed) |
1437 | 0 | { |
1438 | 0 | static char buf[16]; /* enough space to be on the safe side */ |
1439 | 0 | static int last = -1; |
1440 | 0 | const char *space = condensed ? "" : " "; |
1441 | | |
1442 | | /* Trivial optimization. create_image can call us every 200 msecs |
1443 | | (see bar_update) for fast downloads, but ETA will only change |
1444 | | once per 900 msecs. */ |
1445 | 0 | if (secs == last) |
1446 | 0 | return buf; |
1447 | 0 | last = secs; |
1448 | |
|
1449 | 0 | if (secs < 100) |
1450 | 0 | sprintf (buf, "%ds", secs); |
1451 | 0 | else if (secs < 100 * 60) |
1452 | 0 | sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60); |
1453 | 0 | else if (secs < 48 * 3600) |
1454 | 0 | sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60); |
1455 | 0 | else if (secs < 100 * 86400) |
1456 | 0 | sprintf (buf, "%dd%s%dh", secs / 86400, space, (secs / 3600) % 24); |
1457 | 0 | else |
1458 | | /* even (2^31-1)/86400 doesn't overflow BUF. */ |
1459 | 0 | sprintf (buf, "%dd", secs / 86400); |
1460 | |
|
1461 | 0 | return buf; |
1462 | 0 | } |