1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 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.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 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24#include "tool_setup.h" 25#include "tool_operate.h" 26#include "tool_progress.h" 27#include "tool_util.h" 28 29#define ENABLE_CURLX_PRINTF 30/* use our own printf() functions */ 31#include "curlx.h" 32 33/* The point of this function would be to return a string of the input data, 34 but never longer than 5 columns (+ one zero byte). 35 Add suffix k, M, G when suitable... */ 36static char *max5data(curl_off_t bytes, char *max5) 37{ 38#define ONE_KILOBYTE CURL_OFF_T_C(1024) 39#define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) 40#define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) 41#define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) 42#define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) 43 44 if(bytes < CURL_OFF_T_C(100000)) 45 msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); 46 47 else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) 48 msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); 49 50 else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) 51 /* 'XX.XM' is good as long as we're less than 100 megs */ 52 msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" 53 CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, 54 (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); 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 can 79 hold, but our data type is signed so 8192PB will be the maximum. */ 80 return max5; 81} 82 83int xferinfo_cb(void *clientp, 84 curl_off_t dltotal, 85 curl_off_t dlnow, 86 curl_off_t ultotal, 87 curl_off_t ulnow) 88{ 89 struct per_transfer *per = clientp; 90 struct OperationConfig *config = per->config; 91 per->dltotal = dltotal; 92 per->dlnow = dlnow; 93 per->ultotal = ultotal; 94 per->ulnow = ulnow; 95 96 if(per->abort) 97 return 1; 98 99 if(config->readbusy) { 100 config->readbusy = FALSE; 101 curl_easy_pause(per->curl, CURLPAUSE_CONT); 102 } 103 104 return 0; 105} 106 107/* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero 108 byte) */ 109static void time2str(char *r, curl_off_t seconds) 110{ 111 curl_off_t h; 112 if(seconds <= 0) { 113 strcpy(r, "--:--:--"); 114 return; 115 } 116 h = seconds / CURL_OFF_T_C(3600); 117 if(h <= CURL_OFF_T_C(99)) { 118 curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); 119 curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); 120 msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T 121 ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); 122 } 123 else { 124 /* this equals to more than 99 hours, switch to a more suitable output 125 format to fit within the limits. */ 126 curl_off_t d = seconds / CURL_OFF_T_C(86400); 127 h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); 128 if(d <= CURL_OFF_T_C(999)) 129 msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T 130 "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); 131 else 132 msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); 133 } 134} 135 136static curl_off_t all_dltotal = 0; 137static curl_off_t all_ultotal = 0; 138static curl_off_t all_dlalready = 0; 139static curl_off_t all_ulalready = 0; 140 141curl_off_t all_xfers = 0; /* current total */ 142 143struct speedcount { 144 curl_off_t dl; 145 curl_off_t ul; 146 struct timeval stamp; 147}; 148#define SPEEDCNT 10 149static unsigned int speedindex; 150static bool indexwrapped; 151static struct speedcount speedstore[SPEEDCNT]; 152 153/* 154 |DL% UL% Dled Uled Xfers Live Total Current Left Speed 155 | 6 -- 9.9G 0 2 2 0:00:40 0:00:02 0:00:37 4087M 156*/ 157bool progress_meter(struct GlobalConfig *global, 158 struct timeval *start, 159 bool final) 160{ 161 static struct timeval stamp; 162 static bool header = FALSE; 163 struct timeval now; 164 long diff; 165 166 if(global->noprogress || global->silent) 167 return FALSE; 168 169 now = tvnow(); 170 diff = tvdiff(now, stamp); 171 172 if(!header) { 173 header = TRUE; 174 fputs("DL% UL% Dled Uled Xfers Live " 175 "Total Current Left Speed\n", 176 tool_stderr); 177 } 178 if(final || (diff > 500)) { 179 char time_left[10]; 180 char time_total[10]; 181 char time_spent[10]; 182 char buffer[3][6]; 183 curl_off_t spent = tvdiff(now, *start)/1000; 184 char dlpercen[4]="--"; 185 char ulpercen[4]="--"; 186 struct per_transfer *per; 187 curl_off_t all_dlnow = 0; 188 curl_off_t all_ulnow = 0; 189 bool dlknown = TRUE; 190 bool ulknown = TRUE; 191 curl_off_t all_running = 0; /* in progress */ 192 curl_off_t speed = 0; 193 unsigned int i; 194 stamp = now; 195 196 /* first add the amounts of the already completed transfers */ 197 all_dlnow += all_dlalready; 198 all_ulnow += all_ulalready; 199 200 for(per = transfers; per; per = per->next) { 201 all_dlnow += per->dlnow; 202 all_ulnow += per->ulnow; 203 if(!per->dltotal) 204 dlknown = FALSE; 205 else if(!per->dltotal_added) { 206 /* only add this amount once */ 207 all_dltotal += per->dltotal; 208 per->dltotal_added = TRUE; 209 } 210 if(!per->ultotal) 211 ulknown = FALSE; 212 else if(!per->ultotal_added) { 213 /* only add this amount once */ 214 all_ultotal += per->ultotal; 215 per->ultotal_added = TRUE; 216 } 217 if(per->added) 218 all_running++; 219 } 220 if(dlknown && all_dltotal) 221 /* TODO: handle integer overflow */ 222 msnprintf(dlpercen, sizeof(dlpercen), "%3" CURL_FORMAT_CURL_OFF_T, 223 all_dlnow * 100 / all_dltotal); 224 if(ulknown && all_ultotal) 225 /* TODO: handle integer overflow */ 226 msnprintf(ulpercen, sizeof(ulpercen), "%3" CURL_FORMAT_CURL_OFF_T, 227 all_ulnow * 100 / all_ultotal); 228 229 /* get the transfer speed, the higher of the two */ 230 231 i = speedindex; 232 speedstore[i].dl = all_dlnow; 233 speedstore[i].ul = all_ulnow; 234 speedstore[i].stamp = now; 235 if(++speedindex >= SPEEDCNT) { 236 indexwrapped = TRUE; 237 speedindex = 0; 238 } 239 240 { 241 long deltams; 242 curl_off_t dl; 243 curl_off_t ul; 244 curl_off_t dls; 245 curl_off_t uls; 246 if(indexwrapped) { 247 /* 'speedindex' is the oldest stored data */ 248 deltams = tvdiff(now, speedstore[speedindex].stamp); 249 dl = all_dlnow - speedstore[speedindex].dl; 250 ul = all_ulnow - speedstore[speedindex].ul; 251 } 252 else { 253 /* since the beginning */ 254 deltams = tvdiff(now, *start); 255 dl = all_dlnow; 256 ul = all_ulnow; 257 } 258 if(!deltams) /* no division by zero please */ 259 deltams++; 260 dls = (curl_off_t)((double)dl / ((double)deltams/1000.0)); 261 uls = (curl_off_t)((double)ul / ((double)deltams/1000.0)); 262 speed = dls > uls ? dls : uls; 263 } 264 265 266 if(dlknown && speed) { 267 curl_off_t est = all_dltotal / speed; 268 curl_off_t left = (all_dltotal - all_dlnow) / speed; 269 time2str(time_left, left); 270 time2str(time_total, est); 271 } 272 else { 273 time2str(time_left, 0); 274 time2str(time_total, 0); 275 } 276 time2str(time_spent, spent); 277 278 fprintf(tool_stderr, 279 "\r" 280 "%-3s " /* percent downloaded */ 281 "%-3s " /* percent uploaded */ 282 "%s " /* Dled */ 283 "%s " /* Uled */ 284 "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */ 285 "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */ 286 " %s " /* Total time */ 287 "%s " /* Current time */ 288 "%s " /* Time left */ 289 "%s " /* Speed */ 290 "%5s" /* final newline */, 291 292 dlpercen, /* 3 letters */ 293 ulpercen, /* 3 letters */ 294 max5data(all_dlnow, buffer[0]), 295 max5data(all_ulnow, buffer[1]), 296 all_xfers, 297 all_running, 298 time_total, 299 time_spent, 300 time_left, 301 max5data(speed, buffer[2]), /* speed */ 302 final ? "\n" :""); 303 return TRUE; 304 } 305 return FALSE; 306} 307 308void progress_finalize(struct per_transfer *per) 309{ 310 /* get the numbers before this transfer goes away */ 311 all_dlalready += per->dlnow; 312 all_ulalready += per->ulnow; 313 if(!per->dltotal_added) { 314 all_dltotal += per->dltotal; 315 per->dltotal_added = TRUE; 316 } 317 if(!per->ultotal_added) { 318 all_ultotal += per->ultotal; 319 per->ultotal_added = TRUE; 320 } 321} 322