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