| 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 |  | 
|---|