|  | /*************************************************************************** | 
|  | *                                  _   _ ____  _ | 
|  | *  Project                     ___| | | |  _ \| | | 
|  | *                             / __| | | | |_) | | | 
|  | *                            | (__| |_| |  _ <| |___ | 
|  | *                             \___|\___/|_| \_\_____| | 
|  | * | 
|  | * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. | 
|  | * | 
|  | * This software is licensed as described in the file COPYING, which | 
|  | * you should have received as part of this distribution. The terms | 
|  | * are also available at https://curl.se/docs/copyright.html. | 
|  | * | 
|  | * You may opt to use, copy, modify, merge, publish, distribute and/or sell | 
|  | * copies of the Software, and permit persons to whom the Software is | 
|  | * furnished to do so, under the terms of the COPYING file. | 
|  | * | 
|  | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | 
|  | * KIND, either express or implied. | 
|  | * | 
|  | * SPDX-License-Identifier: curl | 
|  | * | 
|  | ***************************************************************************/ | 
|  | #include "tool_setup.h" | 
|  | #include "tool_operate.h" | 
|  | #include "tool_progress.h" | 
|  | #include "tool_util.h" | 
|  |  | 
|  | #define ENABLE_CURLX_PRINTF | 
|  | /* use our own printf() functions */ | 
|  | #include "curlx.h" | 
|  |  | 
|  | /* The point of this function would be to return a string of the input data, | 
|  | but never longer than 5 columns (+ one zero byte). | 
|  | Add suffix k, M, G when suitable... */ | 
|  | static char *max5data(curl_off_t bytes, char *max5) | 
|  | { | 
|  | #define ONE_KILOBYTE  CURL_OFF_T_C(1024) | 
|  | #define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) | 
|  | #define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) | 
|  | #define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) | 
|  | #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) | 
|  |  | 
|  | if(bytes < CURL_OFF_T_C(100000)) | 
|  | msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) | 
|  | /* 'XX.XM' is good as long as we're less than 100 megs */ | 
|  | msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" | 
|  | CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, | 
|  | (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); | 
|  |  | 
|  | #if (SIZEOF_CURL_OFF_T > 4) | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) | 
|  | /* 'XXXXM' is good until we're at 10000MB or above */ | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) | 
|  | /* 10000 MB - 100 GB, we show it as XX.XG */ | 
|  | msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" | 
|  | CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE, | 
|  | (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) | 
|  | /* up to 10000GB, display without decimal: XXXXG */ | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); | 
|  |  | 
|  | else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) | 
|  | /* up to 10000TB, display without decimal: XXXXT */ | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); | 
|  |  | 
|  | else | 
|  | /* up to 10000PB, display without decimal: XXXXP */ | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE); | 
|  |  | 
|  | /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number | 
|  | can hold, but our data type is signed so 8192PB will be the maximum. */ | 
|  |  | 
|  | #else | 
|  |  | 
|  | else | 
|  | msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); | 
|  |  | 
|  | #endif | 
|  |  | 
|  | return max5; | 
|  | } | 
|  |  | 
|  | int xferinfo_cb(void *clientp, | 
|  | curl_off_t dltotal, | 
|  | curl_off_t dlnow, | 
|  | curl_off_t ultotal, | 
|  | curl_off_t ulnow) | 
|  | { | 
|  | struct per_transfer *per = clientp; | 
|  | struct OperationConfig *config = per->config; | 
|  | per->dltotal = dltotal; | 
|  | per->dlnow = dlnow; | 
|  | per->ultotal = ultotal; | 
|  | per->ulnow = ulnow; | 
|  |  | 
|  | if(per->abort) | 
|  | return 1; | 
|  |  | 
|  | if(config->readbusy) { | 
|  | config->readbusy = FALSE; | 
|  | curl_easy_pause(per->curl, CURLPAUSE_CONT); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero | 
|  | byte) */ | 
|  | static void time2str(char *r, curl_off_t seconds) | 
|  | { | 
|  | curl_off_t h; | 
|  | if(seconds <= 0) { | 
|  | strcpy(r, "--:--:--"); | 
|  | return; | 
|  | } | 
|  | h = seconds / CURL_OFF_T_C(3600); | 
|  | if(h <= CURL_OFF_T_C(99)) { | 
|  | curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); | 
|  | curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); | 
|  | msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T | 
|  | ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); | 
|  | } | 
|  | else { | 
|  | /* this equals to more than 99 hours, switch to a more suitable output | 
|  | format to fit within the limits. */ | 
|  | curl_off_t d = seconds / CURL_OFF_T_C(86400); | 
|  | h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); | 
|  | if(d <= CURL_OFF_T_C(999)) | 
|  | msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T | 
|  | "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); | 
|  | else | 
|  | msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); | 
|  | } | 
|  | } | 
|  |  | 
|  | static curl_off_t all_dltotal = 0; | 
|  | static curl_off_t all_ultotal = 0; | 
|  | static curl_off_t all_dlalready = 0; | 
|  | static curl_off_t all_ulalready = 0; | 
|  |  | 
|  | curl_off_t all_xfers = 0;   /* current total */ | 
|  |  | 
|  | struct speedcount { | 
|  | curl_off_t dl; | 
|  | curl_off_t ul; | 
|  | struct timeval stamp; | 
|  | }; | 
|  | #define SPEEDCNT 10 | 
|  | static unsigned int speedindex; | 
|  | static bool indexwrapped; | 
|  | static struct speedcount speedstore[SPEEDCNT]; | 
|  |  | 
|  | /* | 
|  | |DL% UL%  Dled  Uled  Xfers  Live Total     Current  Left    Speed | 
|  | |  6 --   9.9G     0     2     2   0:00:40  0:00:02  0:00:37 4087M | 
|  | */ | 
|  | bool progress_meter(struct GlobalConfig *global, | 
|  | struct timeval *start, | 
|  | bool final) | 
|  | { | 
|  | static struct timeval stamp; | 
|  | static bool header = FALSE; | 
|  | struct timeval now; | 
|  | long diff; | 
|  |  | 
|  | if(global->noprogress) | 
|  | return FALSE; | 
|  |  | 
|  | now = tvnow(); | 
|  | diff = tvdiff(now, stamp); | 
|  |  | 
|  | if(!header) { | 
|  | header = TRUE; | 
|  | fputs("DL% UL%  Dled  Uled  Xfers  Live " | 
|  | "Total     Current  Left    Speed\n", | 
|  | global->errors); | 
|  | } | 
|  | if(final || (diff > 500)) { | 
|  | char time_left[10]; | 
|  | char time_total[10]; | 
|  | char time_spent[10]; | 
|  | char buffer[3][6]; | 
|  | curl_off_t spent = tvdiff(now, *start)/1000; | 
|  | char dlpercen[4]="--"; | 
|  | char ulpercen[4]="--"; | 
|  | struct per_transfer *per; | 
|  | curl_off_t all_dlnow = 0; | 
|  | curl_off_t all_ulnow = 0; | 
|  | bool dlknown = TRUE; | 
|  | bool ulknown = TRUE; | 
|  | curl_off_t all_running = 0; /* in progress */ | 
|  | curl_off_t speed = 0; | 
|  | unsigned int i; | 
|  | stamp = now; | 
|  |  | 
|  | /* first add the amounts of the already completed transfers */ | 
|  | all_dlnow += all_dlalready; | 
|  | all_ulnow += all_ulalready; | 
|  |  | 
|  | for(per = transfers; per; per = per->next) { | 
|  | all_dlnow += per->dlnow; | 
|  | all_ulnow += per->ulnow; | 
|  | if(!per->dltotal) | 
|  | dlknown = FALSE; | 
|  | else if(!per->dltotal_added) { | 
|  | /* only add this amount once */ | 
|  | all_dltotal += per->dltotal; | 
|  | per->dltotal_added = TRUE; | 
|  | } | 
|  | if(!per->ultotal) | 
|  | ulknown = FALSE; | 
|  | else if(!per->ultotal_added) { | 
|  | /* only add this amount once */ | 
|  | all_ultotal += per->ultotal; | 
|  | per->ultotal_added = TRUE; | 
|  | } | 
|  | if(per->added) | 
|  | all_running++; | 
|  | } | 
|  | if(dlknown && all_dltotal) | 
|  | /* TODO: handle integer overflow */ | 
|  | msnprintf(dlpercen, sizeof(dlpercen), "%3" CURL_FORMAT_CURL_OFF_T, | 
|  | all_dlnow * 100 / all_dltotal); | 
|  | if(ulknown && all_ultotal) | 
|  | /* TODO: handle integer overflow */ | 
|  | msnprintf(ulpercen, sizeof(ulpercen), "%3" CURL_FORMAT_CURL_OFF_T, | 
|  | all_ulnow * 100 / all_ultotal); | 
|  |  | 
|  | /* get the transfer speed, the higher of the two */ | 
|  |  | 
|  | i = speedindex; | 
|  | speedstore[i].dl = all_dlnow; | 
|  | speedstore[i].ul = all_ulnow; | 
|  | speedstore[i].stamp = now; | 
|  | if(++speedindex >= SPEEDCNT) { | 
|  | indexwrapped = TRUE; | 
|  | speedindex = 0; | 
|  | } | 
|  |  | 
|  | { | 
|  | long deltams; | 
|  | curl_off_t dl; | 
|  | curl_off_t ul; | 
|  | curl_off_t dls; | 
|  | curl_off_t uls; | 
|  | if(indexwrapped) { | 
|  | /* 'speedindex' is the oldest stored data */ | 
|  | deltams = tvdiff(now, speedstore[speedindex].stamp); | 
|  | dl = all_dlnow - speedstore[speedindex].dl; | 
|  | ul = all_ulnow - speedstore[speedindex].ul; | 
|  | } | 
|  | else { | 
|  | /* since the beginning */ | 
|  | deltams = tvdiff(now, *start); | 
|  | dl = all_dlnow; | 
|  | ul = all_ulnow; | 
|  | } | 
|  | if(!deltams) /* no division by zero please */ | 
|  | deltams++; | 
|  | dls = (curl_off_t)((double)dl / ((double)deltams/1000.0)); | 
|  | uls = (curl_off_t)((double)ul / ((double)deltams/1000.0)); | 
|  | speed = dls > uls ? dls : uls; | 
|  | } | 
|  |  | 
|  |  | 
|  | if(dlknown && speed) { | 
|  | curl_off_t est = all_dltotal / speed; | 
|  | curl_off_t left = (all_dltotal - all_dlnow) / speed; | 
|  | time2str(time_left, left); | 
|  | time2str(time_total, est); | 
|  | } | 
|  | else { | 
|  | time2str(time_left, 0); | 
|  | time2str(time_total, 0); | 
|  | } | 
|  | time2str(time_spent, spent); | 
|  |  | 
|  | fprintf(global->errors, | 
|  | "\r" | 
|  | "%-3s " /* percent downloaded */ | 
|  | "%-3s " /* percent uploaded */ | 
|  | "%s " /* Dled */ | 
|  | "%s " /* Uled */ | 
|  | "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */ | 
|  | "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */ | 
|  | " %s "  /* Total time */ | 
|  | "%s "  /* Current time */ | 
|  | "%s "  /* Time left */ | 
|  | "%s "  /* Speed */ | 
|  | "%5s" /* final newline */, | 
|  |  | 
|  | dlpercen,  /* 3 letters */ | 
|  | ulpercen,  /* 3 letters */ | 
|  | max5data(all_dlnow, buffer[0]), | 
|  | max5data(all_ulnow, buffer[1]), | 
|  | all_xfers, | 
|  | all_running, | 
|  | time_total, | 
|  | time_spent, | 
|  | time_left, | 
|  | max5data(speed, buffer[2]), /* speed */ | 
|  | final ? "\n" :""); | 
|  | return TRUE; | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | void progress_finalize(struct per_transfer *per) | 
|  | { | 
|  | /* get the numbers before this transfer goes away */ | 
|  | all_dlalready += per->dlnow; | 
|  | all_ulalready += per->ulnow; | 
|  | if(!per->dltotal_added) { | 
|  | all_dltotal += per->dltotal; | 
|  | per->dltotal_added = TRUE; | 
|  | } | 
|  | if(!per->ultotal_added) { | 
|  | all_ultotal += per->ultotal; | 
|  | per->ultotal_added = TRUE; | 
|  | } | 
|  | } |