1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.haxx.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | #include "tool_setup.h" |
23 | #include "tool_operate.h" |
24 | #include "tool_progress.h" |
25 | #include "tool_util.h" |
26 | |
27 | #define ENABLE_CURLX_PRINTF |
28 | /* use our own printf() functions */ |
29 | #include "curlx.h" |
30 | |
31 | /* The point of this function would be to return a string of the input data, |
32 | but never longer than 5 columns (+ one zero byte). |
33 | Add suffix k, M, G when suitable... */ |
34 | static char *max5data(curl_off_t bytes, char *max5) |
35 | { |
36 | #define ONE_KILOBYTE CURL_OFF_T_C(1024) |
37 | #define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) |
38 | #define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) |
39 | #define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) |
40 | #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) |
41 | |
42 | if(bytes < CURL_OFF_T_C(100000)) |
43 | msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); |
44 | |
45 | else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) |
46 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k" , bytes/ONE_KILOBYTE); |
47 | |
48 | else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) |
49 | /* 'XX.XM' is good as long as we're less than 100 megs */ |
50 | msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" |
51 | CURL_FORMAT_CURL_OFF_T "M" , bytes/ONE_MEGABYTE, |
52 | (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); |
53 | |
54 | #if (CURL_SIZEOF_CURL_OFF_T > 4) |
55 | |
56 | else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) |
57 | /* 'XXXXM' is good until we're at 10000MB or above */ |
58 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M" , bytes/ONE_MEGABYTE); |
59 | |
60 | else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) |
61 | /* 10000 MB - 100 GB, we show it as XX.XG */ |
62 | msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" |
63 | CURL_FORMAT_CURL_OFF_T "G" , bytes/ONE_GIGABYTE, |
64 | (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); |
65 | |
66 | else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) |
67 | /* up to 10000GB, display without decimal: XXXXG */ |
68 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G" , bytes/ONE_GIGABYTE); |
69 | |
70 | else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) |
71 | /* up to 10000TB, display without decimal: XXXXT */ |
72 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T" , bytes/ONE_TERABYTE); |
73 | |
74 | else |
75 | /* up to 10000PB, display without decimal: XXXXP */ |
76 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P" , bytes/ONE_PETABYTE); |
77 | |
78 | /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number |
79 | can hold, but our data type is signed so 8192PB will be the maximum. */ |
80 | |
81 | #else |
82 | |
83 | else |
84 | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M" , bytes/ONE_MEGABYTE); |
85 | |
86 | #endif |
87 | |
88 | return max5; |
89 | } |
90 | |
91 | int xferinfo_cb(void *clientp, |
92 | curl_off_t dltotal, |
93 | curl_off_t dlnow, |
94 | curl_off_t ultotal, |
95 | curl_off_t ulnow) |
96 | { |
97 | struct per_transfer *per = clientp; |
98 | struct OutStruct *outs = &per->outs; |
99 | struct OperationConfig *config = outs->config; |
100 | per->dltotal = dltotal; |
101 | per->dlnow = dlnow; |
102 | per->ultotal = ultotal; |
103 | per->ulnow = ulnow; |
104 | |
105 | if(config->readbusy) { |
106 | config->readbusy = FALSE; |
107 | curl_easy_pause(per->curl, CURLPAUSE_CONT); |
108 | } |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero |
114 | byte) */ |
115 | static void time2str(char *r, curl_off_t seconds) |
116 | { |
117 | curl_off_t h; |
118 | if(seconds <= 0) { |
119 | strcpy(r, "--:--:--" ); |
120 | return; |
121 | } |
122 | h = seconds / CURL_OFF_T_C(3600); |
123 | if(h <= CURL_OFF_T_C(99)) { |
124 | curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); |
125 | curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); |
126 | msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T |
127 | ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); |
128 | } |
129 | else { |
130 | /* this equals to more than 99 hours, switch to a more suitable output |
131 | format to fit within the limits. */ |
132 | curl_off_t d = seconds / CURL_OFF_T_C(86400); |
133 | h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); |
134 | if(d <= CURL_OFF_T_C(999)) |
135 | msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T |
136 | "d %02" CURL_FORMAT_CURL_OFF_T "h" , d, h); |
137 | else |
138 | msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d" , d); |
139 | } |
140 | } |
141 | |
142 | static curl_off_t all_dltotal = 0; |
143 | static curl_off_t all_ultotal = 0; |
144 | static curl_off_t all_dlalready = 0; |
145 | static curl_off_t all_ulalready = 0; |
146 | |
147 | curl_off_t all_xfers = 0; /* current total */ |
148 | |
149 | struct speedcount { |
150 | curl_off_t dl; |
151 | curl_off_t ul; |
152 | struct timeval stamp; |
153 | }; |
154 | #define SPEEDCNT 10 |
155 | static unsigned int speedindex; |
156 | static bool indexwrapped; |
157 | static struct speedcount speedstore[SPEEDCNT]; |
158 | |
159 | /* |
160 | |DL% UL% Dled Uled Xfers Live Qd Total Current Left Speed |
161 | | 6 -- 9.9G 0 2 2 0 0:00:40 0:00:02 0:00:37 4087M |
162 | */ |
163 | bool progress_meter(struct GlobalConfig *global, |
164 | struct timeval *start, |
165 | bool final) |
166 | { |
167 | static struct timeval stamp; |
168 | static bool = FALSE; |
169 | struct timeval now; |
170 | long diff; |
171 | |
172 | if(global->noprogress) |
173 | return FALSE; |
174 | |
175 | now = tvnow(); |
176 | diff = tvdiff(now, stamp); |
177 | |
178 | if(!header) { |
179 | header = TRUE; |
180 | fputs("DL% UL% Dled Uled Xfers Live Qd " |
181 | "Total Current Left Speed\n" , |
182 | global->errors); |
183 | } |
184 | if(final || (diff > 500)) { |
185 | char time_left[10]; |
186 | char time_total[10]; |
187 | char time_spent[10]; |
188 | char buffer[3][6]; |
189 | curl_off_t spent = tvdiff(now, *start)/1000; |
190 | char dlpercen[4]="--" ; |
191 | char ulpercen[4]="--" ; |
192 | struct per_transfer *per; |
193 | curl_off_t all_dlnow = 0; |
194 | curl_off_t all_ulnow = 0; |
195 | bool dlknown = TRUE; |
196 | bool ulknown = TRUE; |
197 | curl_off_t all_running = 0; /* in progress */ |
198 | curl_off_t all_queued = 0; /* pending */ |
199 | curl_off_t speed = 0; |
200 | unsigned int i; |
201 | stamp = now; |
202 | |
203 | /* first add the amounts of the already completed transfers */ |
204 | all_dlnow += all_dlalready; |
205 | all_ulnow += all_ulalready; |
206 | |
207 | for(per = transfers; per; per = per->next) { |
208 | all_dlnow += per->dlnow; |
209 | all_ulnow += per->ulnow; |
210 | if(!per->dltotal) |
211 | dlknown = FALSE; |
212 | else if(!per->dltotal_added) { |
213 | /* only add this amount once */ |
214 | all_dltotal += per->dltotal; |
215 | per->dltotal_added = TRUE; |
216 | } |
217 | if(!per->ultotal) |
218 | ulknown = FALSE; |
219 | else if(!per->ultotal_added) { |
220 | /* only add this amount once */ |
221 | all_ultotal += per->ultotal; |
222 | per->ultotal_added = TRUE; |
223 | } |
224 | if(!per->added) |
225 | all_queued++; |
226 | else |
227 | all_running++; |
228 | } |
229 | if(dlknown && all_dltotal) |
230 | /* TODO: handle integer overflow */ |
231 | msnprintf(dlpercen, sizeof(dlpercen), "%3d" , |
232 | all_dlnow * 100 / all_dltotal); |
233 | if(ulknown && all_ultotal) |
234 | /* TODO: handle integer overflow */ |
235 | msnprintf(ulpercen, sizeof(ulpercen), "%3d" , |
236 | all_ulnow * 100 / all_ultotal); |
237 | |
238 | /* get the transfer speed, the higher of the two */ |
239 | |
240 | i = speedindex; |
241 | speedstore[i].dl = all_dlnow; |
242 | speedstore[i].ul = all_ulnow; |
243 | speedstore[i].stamp = now; |
244 | if(++speedindex >= SPEEDCNT) { |
245 | indexwrapped = TRUE; |
246 | speedindex = 0; |
247 | } |
248 | |
249 | { |
250 | long deltams; |
251 | curl_off_t dl; |
252 | curl_off_t ul; |
253 | curl_off_t dls; |
254 | curl_off_t uls; |
255 | if(indexwrapped) { |
256 | /* 'speedindex' is the oldest stored data */ |
257 | deltams = tvdiff(now, speedstore[speedindex].stamp); |
258 | dl = all_dlnow - speedstore[speedindex].dl; |
259 | ul = all_ulnow - speedstore[speedindex].ul; |
260 | } |
261 | else { |
262 | /* since the beginning */ |
263 | deltams = tvdiff(now, *start); |
264 | dl = all_dlnow; |
265 | ul = all_ulnow; |
266 | } |
267 | dls = (curl_off_t)((double)dl / ((double)deltams/1000.0)); |
268 | uls = (curl_off_t)((double)ul / ((double)deltams/1000.0)); |
269 | speed = dls > uls ? dls : uls; |
270 | } |
271 | |
272 | |
273 | if(dlknown && speed) { |
274 | curl_off_t est = all_dltotal / speed; |
275 | curl_off_t left = (all_dltotal - all_dlnow) / speed; |
276 | time2str(time_left, left); |
277 | time2str(time_total, est); |
278 | } |
279 | else { |
280 | time2str(time_left, 0); |
281 | time2str(time_total, 0); |
282 | } |
283 | time2str(time_spent, spent); |
284 | |
285 | fprintf(global->errors, |
286 | "\r" |
287 | "%-3s " /* percent downloaded */ |
288 | "%-3s " /* percent uploaded */ |
289 | "%s " /* Dled */ |
290 | "%s " /* Uled */ |
291 | "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */ |
292 | "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */ |
293 | "%5" CURL_FORMAT_CURL_OFF_T " " /* Queued */ |
294 | "%s " /* Total time */ |
295 | "%s " /* Current time */ |
296 | "%s " /* Time left */ |
297 | "%s " /* Speed */ |
298 | "%5s" /* final newline */, |
299 | |
300 | dlpercen, /* 3 letters */ |
301 | ulpercen, /* 3 letters */ |
302 | max5data(all_dlnow, buffer[0]), |
303 | max5data(all_ulnow, buffer[1]), |
304 | all_xfers, |
305 | all_running, |
306 | all_queued, |
307 | time_total, |
308 | time_spent, |
309 | time_left, |
310 | max5data(speed, buffer[2]), /* speed */ |
311 | final ? "\n" :"" ); |
312 | return TRUE; |
313 | } |
314 | return FALSE; |
315 | } |
316 | |
317 | void progress_finalize(struct per_transfer *per) |
318 | { |
319 | /* get the numbers before this transfer goes away */ |
320 | all_dlalready += per->dlnow; |
321 | all_ulalready += per->ulnow; |
322 | } |
323 | |