| /*************************************************************************** | |
| * _ _ ____ _ | |
| * Project ___| | | | _ \| | | |
| * / __| | | | |_) | | | |
| * | (__| |_| | _ <| |___ | |
| * \___|\___/|_| \_\_____| | |
| * | |
| * Copyright (C) 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" | |
| /* 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... | |
| Unit test 1622 | |
| */ | |
| UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen) | |
| { | |
| /* a signed 64-bit value is 8192 petabytes maximum */ | |
| static const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; | |
| int k = 0; | |
| if(bytes < 100000) { | |
| curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); | |
| return max5; | |
| } | |
| do { | |
| curl_off_t nbytes = bytes / 1024; | |
| if(nbytes < 100) { | |
| /* display with a decimal */ | |
| curl_msnprintf(max5, mlen, "%2" CURL_FORMAT_CURL_OFF_T ".%" | |
| CURL_FORMAT_CURL_OFF_T "%c", bytes / 1024, | |
| (bytes % 1024) * 10 / 1024, unit[k]); | |
| break; | |
| } | |
| else if(nbytes < 10000) { | |
| /* no decimals */ | |
| curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, | |
| unit[k]); | |
| break; | |
| } | |
| bytes = nbytes; | |
| k++; | |
| DEBUGASSERT(unit[k]); | |
| } while(unit[k]); | |
| 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 time string that is 8 letters long (plus the zero byte) | |
| Unit test 1622 | |
| */ | |
| UNITTEST void timebuf(char *r, size_t rlen, curl_off_t seconds) | |
| { | |
| curl_off_t h; | |
| if(seconds <= 0) { | |
| curlx_strcopy(r, rlen, " ", 8); | |
| return; | |
| } | |
| h = seconds / 3600; | |
| if(h <= 99) { | |
| curl_off_t m = (seconds - (h * 3600)) / 60; | |
| curl_off_t s = (seconds - (h * 3600)) - (m * 60); | |
| curl_msnprintf(r, rlen, "%02" FMT_OFF_T ":%02" FMT_OFF_T ":" | |
| "%02" FMT_OFF_T, h, m, s); | |
| } | |
| else { | |
| curl_off_t d = seconds / 86400; | |
| h = (seconds - (d * 86400)) / 3600; | |
| if(d <= 999) | |
| curl_msnprintf(r, rlen, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); | |
| else { /* more than 999 days */ | |
| curl_off_t m = d / 30; | |
| if(m <= 999) { | |
| curl_msnprintf(r, rlen, "%3" FMT_OFF_T "m %02" FMT_OFF_T "d", m, d%30); | |
| } | |
| else { /* more than 999 months */ | |
| curl_off_t y = d / 365; | |
| if(y <= 99999) | |
| curl_msnprintf(r, rlen, "%7" FMT_OFF_T "y", y); | |
| else | |
| curlx_strcopy(r, rlen, " >99999y", 8); | |
| } | |
| } | |
| } | |
| } | |
| 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; | |
| struct speedcount { | |
| curl_off_t dl; | |
| curl_off_t ul; | |
| struct curltime stamp; | |
| }; | |
| #define SPEEDCNT 10 | |
| static unsigned int speedindex; | |
| static bool indexwrapped; | |
| static struct speedcount speedstore[SPEEDCNT]; | |
| static void add_offt(curl_off_t *val, curl_off_t add) | |
| { | |
| if(CURL_OFF_T_MAX - *val < add) | |
| /* maxed out! */ | |
| *val = CURL_OFF_T_MAX; | |
| else | |
| *val += add; | |
| } | |
| /* | |
| |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(CURLM *multi, struct curltime *start, bool final) | |
| { | |
| static struct curltime stamp; | |
| static bool header = FALSE; | |
| struct curltime now; | |
| timediff_t diff; | |
| if(global->noprogress || global->silent) | |
| return FALSE; | |
| now = curlx_now(); | |
| diff = curlx_timediff_ms(now, stamp); | |
| if(!header) { | |
| header = TRUE; | |
| fputs("DL% UL% Dled Uled Xfers Live " | |
| "Total Current Left Speed\n", | |
| tool_stderr); | |
| } | |
| if(final || (diff > 500)) { | |
| char time_left[9]; | |
| char time_total[9]; | |
| char time_spent[9]; | |
| char buffer[3][6]; | |
| curl_off_t spent = curlx_timediff_ms(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; | |
| curl_off_t xfers_added = 0; | |
| curl_off_t xfers_running = 0; | |
| bool dlknown = TRUE; | |
| bool ulknown = TRUE; | |
| curl_off_t speed = 0; | |
| unsigned int i; | |
| stamp = now; | |
| /* first add the amounts of the already completed transfers */ | |
| add_offt(&all_dlnow, all_dlalready); | |
| add_offt(&all_ulnow, all_ulalready); | |
| for(per = transfers; per; per = per->next) { | |
| add_offt(&all_dlnow, per->dlnow); | |
| add_offt(&all_ulnow, per->ulnow); | |
| if(!per->dltotal) | |
| dlknown = FALSE; | |
| else if(!per->dltotal_added) { | |
| /* only add this amount once */ | |
| add_offt(&all_dltotal, per->dltotal); | |
| per->dltotal_added = TRUE; | |
| } | |
| if(!per->ultotal) | |
| ulknown = FALSE; | |
| else if(!per->ultotal_added) { | |
| /* only add this amount once */ | |
| add_offt(&all_ultotal, per->ultotal); | |
| per->ultotal_added = TRUE; | |
| } | |
| } | |
| if(dlknown && all_dltotal) | |
| curl_msnprintf(dlpercen, sizeof(dlpercen), "%3" CURL_FORMAT_CURL_OFF_T, | |
| all_dlnow < (CURL_OFF_T_MAX / 100) ? | |
| (all_dlnow * 100 / all_dltotal) : | |
| (all_dlnow / (all_dltotal / 100))); | |
| if(ulknown && all_ultotal) | |
| curl_msnprintf(ulpercen, sizeof(ulpercen), "%3" CURL_FORMAT_CURL_OFF_T, | |
| all_ulnow < (CURL_OFF_T_MAX / 100) ? | |
| (all_ulnow * 100 / all_ultotal) : | |
| (all_ulnow / (all_ultotal / 100))); | |
| /* 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; | |
| } | |
| { | |
| timediff_t 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 = curlx_timediff_ms(now, speedstore[speedindex].stamp); | |
| dl = all_dlnow - speedstore[speedindex].dl; | |
| ul = all_ulnow - speedstore[speedindex].ul; | |
| } | |
| else { | |
| /* since the beginning */ | |
| deltams = curlx_timediff_ms(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; | |
| timebuf(time_left, sizeof(time_left), left); | |
| timebuf(time_total, sizeof(time_total), est); | |
| } | |
| else { | |
| timebuf(time_left, sizeof(time_left), 0); | |
| timebuf(time_total, sizeof(time_total), 0); | |
| } | |
| timebuf(time_spent, sizeof(time_spent), spent); | |
| (void)curl_multi_get_offt(multi, CURLMINFO_XFERS_ADDED, &xfers_added); | |
| (void)curl_multi_get_offt(multi, CURLMINFO_XFERS_RUNNING, &xfers_running); | |
| curl_mfprintf(tool_stderr, | |
| "\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], sizeof(buffer[0])), | |
| max5data(all_ulnow, buffer[1], sizeof(buffer[1])), | |
| xfers_added, | |
| xfers_running, | |
| time_total, | |
| time_spent, | |
| time_left, | |
| max5data(speed, buffer[2], sizeof(buffer[2])), /* speed */ | |
| final ? "\n" : ""); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| void progress_finalize(struct per_transfer *per) | |
| { | |
| /* get the numbers before this transfer goes away */ | |
| add_offt(&all_dlalready, per->dlnow); | |
| add_offt(&all_ulalready, per->ulnow); | |
| if(!per->dltotal_added) { | |
| add_offt(&all_dltotal, per->dltotal); | |
| per->dltotal_added = TRUE; | |
| } | |
| if(!per->ultotal_added) { | |
| add_offt(&all_ultotal, per->ultotal); | |
| per->ultotal_added = TRUE; | |
| } | |
| } |