1 /*
2 * Copyright (c) 2014 Tim Ruehsen
3 * Copyright (c) 2015-2021 Free Software Foundation, Inc.
4 *
5 * This file is part of libwget.
6 *
7 * Libwget is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser 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 * Libwget 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 Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with libwget. If not, see <https://www.gnu.org/licenses/>.
19 *
20 *
21 * Progress bar routines
22 *
23 * Changelog
24 * 18.10.2014 Tim Ruehsen created from src/bar.c
25 *
26 */
27
28 #include <config.h>
29
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <errno.h>
36 #include <sys/time.h>
37 #include <signal.h>
38 #include <wchar.h>
39
40 #include <wget.h>
41 #include "private.h"
42
43 /**
44 * \file
45 * \brief Progress Bar Routines
46 * \defgroup libwget-progress Progress Display Functions
47 * @{
48 *
49 * Methods for creating and printing a progress bar display.
50 */
51
52
53 // We use enums to define the progress bar parameters because they are the
54 // closest thing we have to defining true constants in C without using
55 // preprocessor macros. The advantage of enums is that they will create a
56 // symbol in the symbol table making debugging a whole lot easier.
57
58 // Define the parameters for how the progress bar looks
59 enum BAR_SIZES {
60 BAR_FILENAME_SIZE = 20,
61 BAR_RATIO_SIZE = 3,
62 BAR_METER_COST = 2,
63 BAR_DOWNBYTES_SIZE = 8,
64 BAR_SPEED_SIZE = 8,
65 };
66
67 // Define the cost (in number of columns) of the progress bar decorations. This
68 // includes all the elements that are not the progress indicator itself.
69 enum BAR_DECOR_SIZE {
70 BAR_DECOR_COST =
71 BAR_FILENAME_SIZE + 1 +
72 BAR_RATIO_SIZE + 2 +
73 BAR_METER_COST + 1 +
74 BAR_DOWNBYTES_SIZE + 1 +
75 BAR_SPEED_SIZE + 3
76 };
77
78 enum SCREEN_WIDTH {
79 DEFAULT_SCREEN_WIDTH = 70,
80 MINIMUM_SCREEN_WIDTH = 45,
81 };
82
83 enum bar_slot_status {
84 EMPTY = 0,
85 DOWNLOADING = 1,
86 COMPLETE = 2
87 };
88
89 /** The settings for drawing the progress bar.
90 *
91 * This includes things like how often it is updated, how many values are
92 * stored in the speed ring, etc.
93 */
94 enum BAR_SETTINGS {
95 /// The number of values to store in the speed ring
96 SPEED_RING_SIZE = 24,
97 };
98
99 typedef struct {
100 char
101 *progress,
102 *filename,
103 speed_buf[BAR_SPEED_SIZE],
104 human_size[BAR_DOWNBYTES_SIZE];
105 uint64_t
106 file_size,
107 time_ring[SPEED_RING_SIZE],
108 bytes_ring[SPEED_RING_SIZE],
109 bytes_downloaded;
110 int
111 ring_pos,
112 ring_used,
113 tick,
114 numfiles;
115 enum bar_slot_status
116 status;
117 bool
118 redraw : 1;
119 } bar_slot;
120
121 struct wget_bar_st {
122 bar_slot
123 *slots;
124 char
125 *progress_mem_holder,
126 *unknown_size,
127 *known_size,
128 *spaces;
129 int
130 nslots,
131 max_width;
132 wget_thread_mutex
133 mutex;
134 };
135
136 static wget_report_speed report_speed_type = WGET_REPORT_SPEED_BYTES;
137 static char report_speed_type_char = 'B';
138 static unsigned short speed_modifier = 1000;
139
140 // The progress bar may be redrawn if the window size changes.
141 // XXX: Don't handle that case currently. Instead, later test
142 // what happens if we don't explicitly redraw in such a case.
143 // For fast downloads, it doesn't matter. For slow downloads,
144 // the progress bar will maybe span across two lines till it
145 // gets redrawn. Ideally, this should be a part of the client
146 // code logic and not in the library.
147 // Tl;dr: Move window size detection to client. Allow client to
148 // specify rate at which speed stats should be updated. Speed
149 // ring size will remain constant (Don't want second heap allocation)
150 // - darnir 29/07/2018
bar_update_speed_stats(bar_slot * slotp)151 static void bar_update_speed_stats(bar_slot *slotp)
152 {
153 int ring_pos = slotp->ring_pos;
154 int ring_used = slotp->ring_used;
155 int next_pos;
156 // In case this function is called with no downloaded bytes,
157 // exit early
158 if (slotp->bytes_downloaded == slotp->bytes_ring[ring_pos]) {
159 return;
160 }
161 uint64_t curtime = wget_get_timemillis();
162
163 // Increment the position pointer
164 if (++ring_pos == SPEED_RING_SIZE)
165 ring_pos = 0;
166
167 slotp->bytes_ring[ring_pos] = slotp->bytes_downloaded;
168 slotp->time_ring[ring_pos] = curtime;
169
170 if (ring_used < SPEED_RING_SIZE) {
171 ring_used++;
172 next_pos = 1;
173 } else {
174 next_pos = (ring_pos + 1 == SPEED_RING_SIZE) ? 0 : ring_pos + 1;
175 }
176
177 if (ring_used < 2) {
178 // Not enough measurements to calculate the speed
179 wget_strlcpy(slotp->speed_buf, " --.-K", sizeof(slotp->speed_buf));
180 } else {
181 size_t bytes = slotp->bytes_ring[ring_pos] - slotp->bytes_ring[next_pos];
182 size_t time = slotp->time_ring[ring_pos] - slotp->time_ring[next_pos];
183 size_t speed = (bytes * speed_modifier) / (time ? time : 1);
184
185 wget_human_readable(slotp->speed_buf, sizeof(slotp->speed_buf), speed);
186 }
187 slotp->ring_pos = ring_pos;
188 slotp->ring_used = ring_used;
189 }
190
191 static volatile sig_atomic_t winsize_changed;
192
193 static inline WGET_GCC_ALWAYS_INLINE void
restore_cursor_position(void)194 restore_cursor_position(void)
195 {
196 // ESC 8: Restore cursor position
197 fputs("\0338", stdout);
198 }
199
200 static inline WGET_GCC_ALWAYS_INLINE void
bar_print_slot(const wget_bar * bar,int slot)201 bar_print_slot(const wget_bar *bar, int slot)
202 {
203 // ESC 7: Save cursor
204 // CSI <n> A: Cursor up
205 // CSI <n> G: Cursor horizontal absolute
206 wget_fprintf(stdout, "\0337\033[%dA\033[1G", bar->nslots - slot);
207 }
208
209 static inline WGET_GCC_ALWAYS_INLINE void
bar_set_progress(const wget_bar * bar,int slot)210 bar_set_progress(const wget_bar *bar, int slot)
211 {
212 bar_slot *slotp = &bar->slots[slot];
213
214 if (slotp->file_size > 0) {
215 size_t bytes = slotp->bytes_downloaded;
216 int cols = (int) ((bytes / (double) slotp->file_size) * bar->max_width);
217 if (cols > bar->max_width)
218 cols = bar->max_width;
219 else if (cols <= 0)
220 cols = 1;
221
222 // Write one extra byte for \0. This has already been accounted for
223 // when initializing the progress storage.
224 memcpy(slotp->progress, bar->known_size, cols - 1);
225 slotp->progress[cols - 1] = '>';
226 if (cols < bar->max_width)
227 memset(slotp->progress + cols, ' ', bar->max_width - cols);
228 } else {
229 int ind = slotp->tick % (bar->max_width * 2 - 6);
230 int pre_space;
231
232 if (ind <= bar->max_width - 3)
233 pre_space = ind;
234 else
235 pre_space = bar->max_width - (ind - bar->max_width + 5);
236
237 memset(slotp->progress, ' ', bar->max_width);
238 memcpy(slotp->progress + pre_space, "<=>", 3);
239 }
240
241 slotp->progress[bar->max_width] = 0;
242 }
243
244 /**
245 * \param[in] s String possibly containing multibyte characters (eg UTF-8)
246 * \param[in] available_space Number of columns available for display of s
247 * \param[out] inspectedp where to store number of characters inspected from s
248 * \param[out] padp where to store amount of white space padding
249 *
250 * Inspect that part of the multibyte string s which will consume up to
251 * available_space columns on the screen
252 * Each multibyte character can consume 0 or more columns on the screen
253 * If the string as displayed is shorter than available_space, padding
254 * will be required
255 *
256 * Starting with the first, each (possibly) multibyte sequence in s is
257 * converted to the corresponding wide character.
258 * Two values are derived in this process:
259 * mblen: length of multi-byte sequence (eg 1 for ordinary ASCII)
260 * wcwidth(wide): number of columns occupied by the wide character (>= 0)
261 * The mblen values are summed up to determine how much of s has been
262 * used in the inspection so far and the wcwidth(wide) values are summed up
263 * to determine the position of a (virtual) cursor in the available space.
264 */
265 static void
bar_inspect_multibyte(char * s,size_t available_space,size_t * inspectedp,size_t * padp)266 bar_inspect_multibyte(char *s, size_t available_space, size_t *inspectedp, size_t *padp)
267 {
268 unsigned int displayed = 0; /* number of columns displayed so far */
269 int inspected = 0; /* total number of bytes inspected from s */
270 wchar_t wide; /* wide character made from initial multibyte section */
271 int mblen; /* length of initial multibyte section which was converted to "wide" */
272 size_t remaining;
273
274 if (!s) {
275 *inspectedp = inspected;
276 *padp = available_space;
277 return;
278 }
279
280 remaining = strlen(s); /* a slight optimization */
281
282 /* while we have another character ... */
283 while ((mblen = mbtowc(&wide, &s[inspected], remaining)) > 0) {
284 int wid = wcwidth(wide);
285
286 /*
287 * If we have filled exactly "available_size" columns
288 * and the next character is a zero-width character ...
289 * ... or ...
290 * if appending the wide character would exceed the given available_space ...
291 */
292 if ((wid == 0 && displayed == available_space) || displayed + wid > available_space)
293 break; /* ... we're done */
294
295 /* we're not done, so advance in s ... */
296 inspected += mblen;
297 remaining -= mblen;
298
299 /* ... and advance cursor */
300 displayed += wid;
301 }
302
303 /*
304 * When we come here, we either have processed the entire multibyte
305 * string, then we will need to pad, or we have filled the available
306 * space, then there will be no padding.
307 */
308 *inspectedp = inspected;
309 *padp = available_space - displayed;
310 }
311
bar_update_slot(const wget_bar * bar,int slot)312 static void bar_update_slot(const wget_bar *bar, int slot)
313 {
314 bar_slot *slotp = &bar->slots[slot];
315
316 // We only print a progress bar for the slot if a context has been
317 // registered for it
318 if (slotp->status == DOWNLOADING || slotp->status == COMPLETE) {
319 uint64_t max, cur;
320 int ratio;
321 size_t consumed, pad;
322
323 max = slotp->file_size;
324 cur = slotp->bytes_downloaded;
325
326 ratio = max ? (int) ((100 * cur) / max) : 0;
327
328 wget_human_readable(slotp->human_size, sizeof(slotp->human_size), cur);
329
330 bar_update_speed_stats(slotp);
331
332 bar_set_progress(bar, slot);
333
334 bar_print_slot(bar, slot);
335
336 // The progress bar looks like this:
337 //
338 // filename xxx% [======> ] xxx.xxK
339 //
340 // It is made of the following elements:
341 // filename _BAR_FILENAME_SIZE Name of local file
342 // xxx% _BAR_RATIO_SIZE + 1 Amount of file downloaded
343 // [] _BAR_METER_COST Bar Decorations
344 // xxx.xxK _BAR_DOWNBYTES_SIZE Number of downloaded bytes
345 // xxx.xxKB/s _BAR_SPEED_SIZE Download speed
346 // ===> Remaining Progress Meter
347
348 bar_inspect_multibyte(slotp->filename, BAR_FILENAME_SIZE, &consumed, &pad);
349 wget_fprintf(stdout, "%-*.*s %*d%% [%s] %*s %*s%c/s",
350 (int) (consumed+pad), (int) (consumed+pad), slotp->filename,
351 BAR_RATIO_SIZE, ratio,
352 slotp->progress,
353 BAR_DOWNBYTES_SIZE, slotp->human_size,
354 BAR_SPEED_SIZE, slotp->speed_buf, report_speed_type_char);
355
356 restore_cursor_position();
357 fflush(stdout);
358 slotp->tick++;
359 }
360 }
361
bar_get_width(void)362 static int bar_get_width(void)
363 {
364 int width = DEFAULT_SCREEN_WIDTH;
365
366 if (wget_get_screen_size(&width, NULL) == 0) {
367 if (width < MINIMUM_SCREEN_WIDTH)
368 width = MINIMUM_SCREEN_WIDTH;
369 else
370 width--; // leave one space at the end, else we see a linebreak on Windows
371 }
372
373 return width - BAR_DECOR_COST;
374 }
375
bar_update_winsize(wget_bar * bar,bool slots_changed)376 static void bar_update_winsize(wget_bar *bar, bool slots_changed)
377 {
378 if (winsize_changed || slots_changed) {
379 char *progress_mem_holder;
380 int max_width = bar_get_width();
381
382 if (!(progress_mem_holder = wget_calloc(bar->nslots, max_width + 1)))
383 return;
384
385 if (bar->max_width < max_width) {
386 char *known_size = wget_malloc(max_width);
387 char *unknown_size = wget_malloc(max_width);
388 char *spaces = wget_malloc(max_width);
389
390 if (!known_size || ! unknown_size || !spaces) {
391 xfree(spaces);
392 xfree(unknown_size);
393 xfree(known_size);
394 xfree(progress_mem_holder);
395 return;
396 }
397
398 xfree(bar->known_size);
399 bar->known_size = known_size;
400 memset(bar->known_size, '=', max_width);
401
402 xfree(bar->unknown_size);
403 bar->unknown_size = unknown_size;
404 memset(bar->unknown_size, '*', max_width);
405
406 xfree(bar->spaces);
407 bar->spaces = spaces;
408 memset(bar->spaces, ' ', max_width);
409 }
410
411 xfree(bar->progress_mem_holder);
412 // Add one extra byte to hold the \0 character
413 bar->progress_mem_holder = progress_mem_holder;
414 for (int i = 0; i < bar->nslots; i++) {
415 bar->slots[i].progress = bar->progress_mem_holder + (i * max_width);
416 }
417
418 bar->max_width = max_width;
419 }
420 winsize_changed = 0;
421 }
422
bar_update(wget_bar * bar)423 static void bar_update(wget_bar *bar)
424 {
425 bar_update_winsize(bar, false);
426 for (int i = 0; i < bar->nslots; i++) {
427 if (bar->slots[i].redraw || winsize_changed) {
428 bar_update_slot(bar, i);
429 bar->slots[i].redraw = 0;
430 }
431 }
432 }
433
434 /**
435 * \param[in] bar Pointer to a \p wget_bar object
436 * \param[in] nslots Number of progress bars
437 * \return Pointer to a \p wget_bar object
438 *
439 * Initialize a new progress bar instance. If \p bar is a NULL
440 * pointer, it will be allocated on the heap and a pointer to the newly
441 * allocated memory will be returned. To free this memory, call either the
442 * wget_bar_deinit() or wget_bar_free() functions based on your needs.
443 *
444 * \p nslots is the number of screen lines to reserve for printing the progress
445 * bars. This may be any number, but you generally want at least as many slots
446 * as there are downloader threads.
447 */
wget_bar_init(wget_bar * bar,int nslots)448 wget_bar *wget_bar_init(wget_bar *bar, int nslots)
449 {
450 /* Initialize screen_width if this hasn't been done or if it might
451 have changed, as indicated by receiving SIGWINCH. */
452 int max_width = bar_get_width();
453
454 if (nslots < 1 || max_width < 1)
455 return NULL;
456
457 if (!bar) {
458 if (!(bar = wget_calloc(1, sizeof(*bar))))
459 return NULL;
460 } else
461 memset(bar, 0, sizeof(*bar));
462
463 wget_thread_mutex_init(&bar->mutex);
464 wget_bar_set_slots(bar, nslots);
465
466 return bar;
467 }
468
469 /**
470 * \param[in] bar Pointer to a wget_bar object
471 * \param[in] nslots The new number of progress bars that should be drawn
472 *
473 * Update the number of progress bar lines that are drawn on the screen.
474 * This is useful when the number of downloader threads changes dynamically or
475 * to change the number of reserved lines. Calling this function will
476 * immediately reserve \p nslots lines on the screen. However if \p nslots is
477 * lower than the existing value, nothing will be done.
478 */
wget_bar_set_slots(wget_bar * bar,int nslots)479 void wget_bar_set_slots(wget_bar *bar, int nslots)
480 {
481 wget_thread_mutex_lock(bar->mutex);
482 int more_slots = nslots - bar->nslots;
483
484 if (more_slots > 0) {
485 bar_slot *slots = wget_realloc(bar->slots, nslots * sizeof(bar_slot));
486 if (!slots) {
487 wget_thread_mutex_unlock(bar->mutex);
488 return;
489 }
490 bar->slots = slots;
491 memset(bar->slots + bar->nslots, 0, more_slots * sizeof(bar_slot));
492 bar->nslots = nslots;
493
494 for (int i = 0; i < more_slots; i++)
495 fputs("\n", stdout);
496
497 bar_update_winsize(bar, true);
498 bar_update(bar);
499 }
500 wget_thread_mutex_unlock(bar->mutex);
501 }
502
503 /**
504 * \param[in] bar Pointer to a wget_bar object
505 * \param[in] slot The slot number to use
506 * \param[in] filename The file name to display in the given \p slot
507 * \param[in] new_file if this is the start of a download of the body of a new file
508 * \param[in] file_size The file size that would be 100%
509 *
510 * Initialize the given \p slot of the \p bar object with it's (file) name to display
511 * and the (file) size to be assumed 100%.
512 */
wget_bar_slot_begin(wget_bar * bar,int slot,const char * filename,int new_file,ssize_t file_size)513 void wget_bar_slot_begin(wget_bar *bar, int slot, const char *filename, int new_file, ssize_t file_size)
514 {
515 wget_thread_mutex_lock(bar->mutex);
516 bar_slot *slotp = &bar->slots[slot];
517
518 xfree(slotp->filename);
519 if (new_file)
520 slotp->numfiles++;
521 if (slotp->numfiles == 1) {
522 slotp->filename = wget_strdup(filename);
523 } else {
524 slotp->filename = wget_aprintf("%d files", slotp->numfiles);
525 }
526 slotp->tick = 0;
527 slotp->file_size += file_size;
528 slotp->status = DOWNLOADING;
529 slotp->redraw = 1;
530 slotp->ring_pos = 0;
531 slotp->ring_used = 0;
532
533 memset(&slotp->time_ring, 0, sizeof(slotp->time_ring));
534 memset(&slotp->bytes_ring, 0, sizeof(slotp->bytes_ring));
535
536 wget_thread_mutex_unlock(bar->mutex);
537 }
538
539 /**
540 * \param[in] bar Pointer to a wget_bar object
541 * \param[in] slot The slot number to use
542 * \param[in] nbytes The number of bytes downloaded since the last invocation of this function
543 *
544 * Set the current number of bytes for \p slot for the next update of
545 * the bar/slot.
546 */
wget_bar_slot_downloaded(wget_bar * bar,int slot,size_t nbytes)547 void wget_bar_slot_downloaded(wget_bar *bar, int slot, size_t nbytes)
548 {
549 wget_thread_mutex_lock(bar->mutex);
550 bar->slots[slot].bytes_downloaded += nbytes;
551 bar->slots[slot].redraw = 1;
552 wget_thread_mutex_unlock(bar->mutex);
553 }
554
555 /**
556 * \param[in] bar Pointer to a wget_bar object
557 * \param[in] slot The slot number to use
558 *
559 * Redraw the given \p slot as being completed.
560 */
wget_bar_slot_deregister(wget_bar * bar,int slot)561 void wget_bar_slot_deregister(wget_bar *bar, int slot)
562 {
563 wget_thread_mutex_lock(bar->mutex);
564 if (slot >= 0 && slot < bar->nslots) {
565 bar_slot *slotp = &bar->slots[slot];
566
567 slotp->status = COMPLETE;
568 bar_update_slot(bar, slot);
569 }
570 wget_thread_mutex_unlock(bar->mutex);
571 }
572
573 /**
574 * \param[in] bar Pointer to a wget_bar object
575 *
576 * Redraw the parts of the \p bar that have been changed so far.
577 */
wget_bar_update(wget_bar * bar)578 void wget_bar_update(wget_bar *bar)
579 {
580 wget_thread_mutex_lock(bar->mutex);
581 bar_update(bar);
582 wget_thread_mutex_unlock(bar->mutex);
583 }
584
585 /**
586 * \param[in] bar Pointer to \p wget_bar
587 *
588 * Free the various progress bar data structures
589 * without freeing \p bar itself.
590 */
wget_bar_deinit(wget_bar * bar)591 void wget_bar_deinit(wget_bar *bar)
592 {
593 if (bar) {
594 for (int i = 0; i < bar->nslots; i++) {
595 xfree(bar->slots[i].filename);
596 }
597 xfree(bar->progress_mem_holder);
598 xfree(bar->spaces);
599 xfree(bar->known_size);
600 xfree(bar->unknown_size);
601 xfree(bar->slots);
602 wget_thread_mutex_destroy(&bar->mutex);
603 }
604 }
605
606 /**
607 * \param[in] bar Pointer to \p wget_bar
608 *
609 * Free the various progress bar data structures
610 * including the \p bar pointer itself.
611 */
wget_bar_free(wget_bar ** bar)612 void wget_bar_free(wget_bar **bar)
613 {
614 if (bar) {
615 wget_bar_deinit(*bar);
616 xfree(*bar);
617 }
618 }
619
620 /**
621 * \param[in] bar Pointer to \p wget_bar
622 * \param[in] slot The slot number to use
623 * \param[in] display The string to be displayed in the given slot
624 *
625 * Displays the \p display string in the given \p slot.
626 */
wget_bar_print(wget_bar * bar,int slot,const char * display)627 void wget_bar_print(wget_bar *bar, int slot, const char *display)
628 {
629 wget_thread_mutex_lock(bar->mutex);
630 bar_print_slot(bar, slot);
631 // CSI <n> G: Cursor horizontal absolute
632 wget_fprintf(stdout, "\033[27G[%-*.*s]", bar->max_width, bar->max_width, display);
633 restore_cursor_position();
634 fflush(stdout);
635 wget_thread_mutex_unlock(bar->mutex);
636 }
637
638 /**
639 * \param[in] bar Pointer to \p wget_bar
640 * \param[in] slot The slot number to use
641 * \param[in] fmt Printf-like format to build the display string
642 * \param[in] args Arguments matching the \p fmt format string
643 *
644 * Displays the \p string build using the printf-style \p fmt and \p args.
645 */
wget_bar_vprintf(wget_bar * bar,int slot,const char * fmt,va_list args)646 void wget_bar_vprintf(wget_bar *bar, int slot, const char *fmt, va_list args)
647 {
648 char text[bar->max_width + 1];
649
650 wget_vsnprintf(text, sizeof(text), fmt, args);
651 wget_bar_print(bar, slot, text);
652 }
653
654 /**
655 * \param[in] bar Pointer to \p wget_bar
656 * \param[in] slot The slot number to use
657 * \param[in] fmt Printf-like format to build the display string
658 * \param[in] ... List of arguments to match \p fmt
659 *
660 * Displays the \p string build using the printf-style \p fmt and the given arguments.
661 */
wget_bar_printf(wget_bar * bar,int slot,const char * fmt,...)662 void wget_bar_printf(wget_bar *bar, int slot, const char *fmt, ...)
663 {
664 va_list args;
665
666 va_start(args, fmt);
667 wget_bar_vprintf(bar, slot, fmt, args);
668 va_end(args);
669 }
670
671 /**
672 * Call this function when a resize of the screen / console has been detected.
673 */
wget_bar_screen_resized(void)674 void wget_bar_screen_resized(void)
675 {
676 winsize_changed = 1;
677 }
678
679 /**
680 *
681 * \param[in] bar Pointer to \p wget_bar
682 * @param buf Pointer to buffer to be displayed
683 * @param len Number of bytes to be displayed
684 *
685 * Write 'above' the progress bar area, scrolls screen one line up
686 * if needed. Currently used by Wget2 to display error messages in
687 * color red.
688 *
689 * This function needs a redesign to be useful for general purposes.
690 */
wget_bar_write_line(wget_bar * bar,const char * buf,size_t len)691 void wget_bar_write_line(wget_bar *bar, const char *buf, size_t len)
692 {
693 wget_thread_mutex_lock(bar->mutex);
694 // ESC 7: Save cursor
695 // CSI <n>S: Scroll up whole screen
696 // CSI <n>A: Cursor up
697 // CSI <n>G: Cursor horizontal absolute
698 // CSI 0J: Clear from cursor to end of screen
699 // CSI 31m: Red text color
700 wget_fprintf(stdout, "\0337\033[1S\033[%dA\033[1G\033[0J\033[31m", bar->nslots + 1);
701 fwrite(buf, 1, len, stdout);
702 fputs("\033[m", stdout); // reset text color
703 restore_cursor_position();
704
705 bar_update(bar);
706 wget_thread_mutex_unlock(bar->mutex);
707 }
708
709 /**
710 * @param type Report speed type
711 *
712 * Set the progress bar report speed type to WGET_REPORT_SPEED_BYTES
713 * or WGET_REPORT_SPEED_BITS.
714 *
715 * Default is WGET_REPORT_SPEED_BYTES.
716 */
wget_bar_set_speed_type(wget_report_speed type)717 void wget_bar_set_speed_type(wget_report_speed type)
718 {
719 report_speed_type = type;
720 if (type == WGET_REPORT_SPEED_BITS) {
721 report_speed_type_char = 'b';
722 speed_modifier = 8;
723 }
724 }
725 /** @}*/
726