File: progressbar.c

package info (click to toggle)
diskscan 0.21-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,656 kB
  • sloc: ansic: 11,136; python: 338; xml: 138; sh: 41; makefile: 34
file content (211 lines) | stat: -rw-r--r-- 6,424 bytes parent folder | download | duplicates (6)
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);
}