1 #include <stdlib.h>
2 #include <string.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <sys/time.h>
6 #include <sys/ioctl.h>
7 #include <inttypes.h>
8 #include <math.h>
9
10 #include "progress.h"
11
gettimed()12 static double gettimed() {
13 struct timeval tv;
14 gettimeofday(&tv, NULL);
15 return (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
16 }
17
get_terminal_width(int fd)18 static int get_terminal_width(int fd) {
19 size_t width = 80;
20
21 #ifdef TIOCGWINSZ
22 struct winsize wsz;
23
24 if(ioctl(fd, TIOCGWINSZ, &wsz) < 0) {
25 /* ignore this error */
26 /* perror("ioctl(TIOCGWINSZ)"); */
27 } else {
28 width = wsz.ws_col;
29 }
30 #endif
31
32 return width;
33 }
34
human_size(char * buf,size_t buf_len,off_t size,int base,const char * posfix)35 static char *human_size(char *buf, size_t buf_len, off_t size, int base, const char *posfix) {
36 char *suf = "KMGTPEZY";
37 char c[3] = "\0\0\0";
38 double size_d = size;
39 char *t;
40
41 while(size_d >= base && *suf) {
42 size_d /= base;
43 c[0] = *suf;
44 suf++;
45 }
46
47 if(c[0] && base == 1024)
48 c[1] = 'i';
49
50 snprintf(buf, buf_len, "%.1f", size_d);
51 t = buf + strlen(buf) - 1;
52
53 while(t >= buf && (*t == '0' || *t == '.')) {
54 if(*t == '.') {
55 *t = '\0';
56 break;
57 }
58 *t = '\0';
59 t--;
60 }
61
62 strncat(buf, " ", buf_len);
63 strncat(buf, c, buf_len);
64 strncat(buf, posfix, buf_len);
65
66 return buf;
67 }
68
human_time(char * buf,size_t buf_len,time_t interval)69 static char *human_time(char *buf, size_t buf_len, time_t interval) {
70 int days = interval / (24*60*60);
71 int hours = (interval % (24*60*60)) / (60*60);
72 int minutes = (interval % (60*60)) / 60;
73 int seconds = interval % 60;
74
75 if(days)
76 snprintf(buf, buf_len, "%02dd %02dh", days, hours);
77 else if(hours)
78 snprintf(buf, buf_len, "%02dh %02dm", hours, minutes);
79 else
80 snprintf(buf, buf_len, "%02dm %02ds", minutes, seconds);
81
82 return buf;
83 }
84
progress_init(struct progress * p,int bar_fd)85 void progress_init(struct progress *p, int bar_fd) {
86 p->bar_fd = bar_fd;
87 p->bar_file = fdopen(bar_fd, "w");
88 setvbuf(p->bar_file, NULL, _IONBF, 0);
89
90 p->size = 0;
91 p->size_cb = NULL;
92 p->size_lines = 0;
93
94 p->current_position = 0;
95
96 p->start_time = gettimed();
97 p->last_update_time = 0;
98
99 p->lines_mode = 0;
100 p->force_done = 0;
101 }
102
progress_free(struct progress * p)103 void progress_free(struct progress *p) {
104 }
105
progress_move(struct progress * p,off_t offset)106 void progress_move(struct progress *p, off_t offset) {
107 p->current_position += offset;
108 }
109
progress_move_lines(struct progress * p,off_t offset)110 void progress_move_lines(struct progress *p, off_t offset) {
111 p->current_position_lines += offset;
112 }
113
progress_draw(struct progress * p)114 void progress_draw(struct progress *p) {
115 char size_buf[32], time_buf[32];
116 char percent_buf[8];
117 char head[64];
118 char tail[64];
119
120 off_t avg_speed;
121 off_t position;
122
123 double complete_state;
124 char *bar;
125 int terminal_width;
126
127 unsigned int bar_full_len, bar_len;
128 off_t size;
129 double ela, eta;
130
131 terminal_width = get_terminal_width(p->bar_fd) - 3;
132 if(terminal_width <= 25)
133 return;
134
135 ela = gettimed() - p->start_time;
136
137 if(p->lines_mode) {
138 size = p->size_lines;
139 position = p->current_position_lines;
140 } else {
141 size = p->size_cb ? p->size_cb() : p->size;
142 position = p->current_position;
143 }
144
145 avg_speed = (off_t)(ela ? position / ela : 0);
146
147 if(!size && p->lines_mode) {
148 if(p->current_position_lines && (p->current_position / p->current_position_lines))
149 size = p->size / (p->current_position / p->current_position_lines);
150 }
151
152 eta = 0;
153
154 if(size && position) {
155 if(position > size)
156 complete_state = 100500.0;
157 else
158 complete_state = (double)position / size;
159
160 eta = (double)size / avg_speed - ela;
161 if(eta < 0)
162 eta = 0;
163 } else {
164 complete_state = -1.0;
165 }
166
167 snprintf(
168 head,
169 sizeof(head),
170 "%10s",
171 human_size(
172 size_buf,
173 sizeof(size_buf),
174 position,
175 p->lines_mode ? 1000 : 1024,
176 p->lines_mode ? "ln" : "B"
177 )
178 );
179
180 if(size) {
181 snprintf(
182 head + strlen(head),
183 sizeof(head) - strlen(head),
184 " / %s%-8s",
185 (p->lines_mode && !p->size_lines) ? "~" : "",
186 human_size(
187 size_buf,
188 sizeof(size_buf),
189 size,
190 p->lines_mode ? 1000 : 1024,
191 p->lines_mode ? "ln" : "B"
192 )
193 );
194 }
195
196 strcat(head, " [");
197
198 snprintf(
199 tail,
200 sizeof(tail),
201 "] %-11s (%s %s)",
202 human_size(
203 size_buf,
204 sizeof(size_buf),
205 avg_speed,
206 p->lines_mode ? 1000 : 1024,
207 p->lines_mode ? "ln/s" : "B/s"
208 ),
209 size && !p->force_done ? "eta" : "ela",
210 human_time(
211 time_buf,
212 sizeof(time_buf),
213 (time_t)(size && !p->force_done ?
214 eta
215 :ela
216 )
217 )
218 );
219
220 if(complete_state < 0) {
221 strncpy(percent_buf, " N/A ", sizeof(percent_buf));
222 } else if(complete_state > 1.0) {
223 strncpy(percent_buf, " >100% ", sizeof(percent_buf));
224 } else {
225 snprintf(
226 percent_buf,
227 sizeof(percent_buf),
228 " %d%% ",
229 p->force_done && p->lines_mode && !p->size_lines ? 100 : (unsigned int)(complete_state * 100)
230 );
231 }
232
233 bar_full_len = terminal_width - (strlen(head) + strlen(tail));
234
235 if(bar_full_len < strlen(percent_buf))
236 bar_full_len = strlen(percent_buf);
237
238 if((p->force_done && p->lines_mode && !p->size_lines) || complete_state > 1.0) {
239 bar_len = bar_full_len;
240 } else {
241 bar_len = (unsigned int)(complete_state * bar_full_len);
242 }
243
244 if(bar_len > bar_full_len)
245 bar_len = bar_full_len;
246
247 bar = (char *)alloca(bar_full_len + 1);
248 memset(bar, '=', bar_len);
249 memset(bar + bar_len, '-', bar_full_len - bar_len);
250 memcpy(bar + bar_full_len / 2 - strlen(percent_buf) / 2, percent_buf, strlen(percent_buf));
251 bar[bar_full_len] = 0;
252
253 fputs("\r", p->bar_file);
254 fputs(head, p->bar_file);
255 fputs(bar, p->bar_file);
256 fputs(tail, p->bar_file);
257 }
258
259 /* */
260