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