1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
/**
* \file
* \author Trevor Fountain
* \author Johannes Buchner
* \author Erik Garrison
* \date 2010-2014
* \copyright BSD 3-Clause
*
* progressbar -- a C class (by convention) for displaying progress
* on the command line (to stderr).
*/
#include <termcap.h> /* tgetent, tgetnum */
#include <assert.h>
#include <limits.h>
#include "progressbar.h"
/// How wide we assume the screen is if termcap fails.
enum { DEFAULT_SCREEN_WIDTH = 80 };
/// The smallest that the bar can ever be (not including borders)
enum { MINIMUM_BAR_WIDTH = 10 };
/// The format in which the estimated remaining time will be reported
static const char *const ETA_FORMAT = "ETA:%2dh%02dm%02ds";
/// The maximum number of characters that the ETA_FORMAT can ever yield
enum { ETA_FORMAT_LENGTH = 13 };
/// Amount of screen width taken up by whitespace (i.e. whitespace between label/bar/ETA components)
enum { WHITESPACE_LENGTH = 2 };
/// The amount of width taken up by the border of the bar component.
enum { BAR_BORDER_WIDTH = 2 };
/// Models a duration of time broken into hour/minute/second components. The number of seconds should be less than the
/// number of seconds in one minute, and the number of minutes should be less than the number of minutes in one hour.
typedef struct {
int hours;
int minutes;
int seconds;
} progressbar_time_components;
static void progressbar_draw(const progressbar *bar);
/**
* Create a new progress bar with the specified label, max number of steps, and format string.
* Note that `format` must be exactly three characters long, e.g. "<->" to render a progress
* bar like "<---------->". Returns NULL if there isn't enough memory to allocate a progressbar
*/
progressbar *progressbar_new_with_format(const char *label, unsigned long max, const char *format)
{
progressbar *new = malloc(sizeof(progressbar));
if(new == NULL) {
return NULL;
}
new->max = max;
new->value = 0;
new->start = time(NULL);
assert(3 == strlen(format) && "format must be 3 characters in length");
new->format.begin = format[0];
new->format.fill = format[1];
new->format.end = format[2];
progressbar_update_label(new, label);
progressbar_draw(new);
return new;
}
/**
* Create a new progress bar with the specified label and max number of steps.
*/
progressbar *progressbar_new(const char *label, unsigned long max)
{
return progressbar_new_with_format(label, max, "|=|");
}
void progressbar_update_label(progressbar *bar, const char *label)
{
bar->label = label;
}
/**
* Delete an existing progress bar.
*/
void progressbar_free(progressbar *bar)
{
free(bar);
}
/**
* Increment an existing progressbar by `value` steps.
*/
void progressbar_update(progressbar *bar, unsigned long value)
{
bar->value = value;
progressbar_draw(bar);
}
/**
* Increment an existing progressbar by a single step.
*/
void progressbar_inc(progressbar *bar)
{
progressbar_update(bar, bar->value+1);
}
static void progressbar_write_char(FILE *file, const int ch, const size_t times) {
size_t i;
for (i = 0; i < times; ++i) {
fputc(ch, file);
}
}
static int progressbar_max(int x, int y) {
return x > y ? x : y;
}
static unsigned int get_screen_width(void) {
char termbuf[2048];
if (tgetent(termbuf, getenv("TERM")) >= 0) {
return tgetnum("co") /* -2 */;
} else {
return DEFAULT_SCREEN_WIDTH;
}
}
static int progressbar_bar_width(int screen_width, int label_length) {
return progressbar_max(MINIMUM_BAR_WIDTH, screen_width - label_length - ETA_FORMAT_LENGTH - WHITESPACE_LENGTH);
}
static int progressbar_label_width(int screen_width, int label_length, int bar_width) {
int eta_width = ETA_FORMAT_LENGTH;
// If the progressbar is too wide to fit on the screen, we must sacrifice the label.
if (label_length + 1 + bar_width + 1 + ETA_FORMAT_LENGTH > screen_width) {
return progressbar_max(0, screen_width - bar_width - eta_width - WHITESPACE_LENGTH);
} else {
return label_length;
}
}
static int progressbar_remaining_seconds(const progressbar* bar) {
double offset = difftime(time(NULL), bar->start);
if (bar->value > 0 && offset > 0) {
return (offset / (double) bar->value) * (bar->max - bar->value);
} else {
return 0;
}
}
static progressbar_time_components progressbar_calc_time_components(int seconds) {
int hours = seconds / 3600;
seconds -= hours * 3600;
int minutes = seconds / 60;
seconds -= minutes * 60;
progressbar_time_components components = {hours, minutes, seconds};
return components;
}
static void progressbar_draw(const progressbar *bar)
{
int screen_width = get_screen_width();
int label_length = strlen(bar->label);
int bar_width = progressbar_bar_width(screen_width, label_length);
int label_width = progressbar_label_width(screen_width, label_length, bar_width);
int progressbar_completed = (bar->value >= bar->max);
int bar_piece_count = bar_width - BAR_BORDER_WIDTH;
int bar_piece_current = (progressbar_completed)
? bar_piece_count
: bar_piece_count * ((double) bar->value / bar->max);
progressbar_time_components eta = (progressbar_completed)
? progressbar_calc_time_components(difftime(time(NULL), bar->start))
: progressbar_calc_time_components(progressbar_remaining_seconds(bar));
if (label_width == 0) {
// The label would usually have a trailing space, but in the case that we don't print
// a label, the bar can use that space instead.
bar_width += 1;
} else {
// Draw the label
fwrite(bar->label, 1, label_width, stderr);
fputc(' ', stderr);
}
// Draw the progressbar
fputc(bar->format.begin, stderr);
progressbar_write_char(stderr, bar->format.fill, bar_piece_current);
progressbar_write_char(stderr, ' ', bar_piece_count - bar_piece_current);
fputc(bar->format.end, stderr);
// Draw the ETA
fputc(' ', stderr);
fprintf(stderr, ETA_FORMAT, eta.hours, eta.minutes, eta.seconds);
fputc('\r', stderr);
}
/**
* Finish a progressbar, indicating 100% completion, and free it.
*/
void progressbar_finish(progressbar *bar)
{
// Make sure we fill the progressbar so things look complete.
progressbar_draw(bar);
// Print a newline, so that future outputs to stderr look prettier
fprintf(stderr, "\n");
// We've finished with this progressbar, so go ahead and free it.
progressbar_free(bar);
}
|