1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
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 http://curl.haxx.se/docs/copyright.html.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
22 ***************************************************************************/
33 #include <curl/curl.h>
38 #define _MPRINTF_REPLACE /* use our functions only */
39 #include <curl/mprintf.h>
41 /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
43 static void time2str(char *r, long t)
47 strcpy(r, "--:--:--");
52 long m = (t-(h*3600))/60;
53 long s = (t-(h*3600)-(m*60));
54 sprintf(r, "%2ld:%02ld:%02ld",h,m,s);
57 /* this equals to more than 99 hours, switch to a more suitable output
58 format to fit within the limits. */
60 sprintf(r, "%3ldd %02ldh", h/24, h-(h/24)*24);
62 sprintf(r, "%7ldd", h/24);
66 /* The point of this function would be to return a string of the input data,
67 but never longer than 5 columns. Add suffix k, M, G when suitable... */
68 static char *max5data(curl_off_t bytes, char *max5)
70 #define ONE_KILOBYTE 1024
71 #define ONE_MEGABYTE (1024*1024)
72 #define ONE_GIGABYTE (1024*1024*1024)
75 sprintf(max5, "%5" FORMAT_OFF_T, bytes);
77 else if(bytes < (10000*ONE_KILOBYTE)) {
78 sprintf(max5, "%4" FORMAT_OFF_T "k", (curl_off_t)(bytes/ONE_KILOBYTE));
80 else if(bytes < (100*ONE_MEGABYTE)) {
81 /* 'XX.XM' is good as long as we're less than 100 megs */
82 sprintf(max5, "%2d.%0dM",
83 (int)(bytes/ONE_MEGABYTE),
84 (int)(bytes%ONE_MEGABYTE)/(ONE_MEGABYTE/10) );
86 #if SIZEOF_CURL_OFF_T > 4
87 else if(bytes < ((curl_off_t)10000*ONE_MEGABYTE)) {
88 sprintf(max5, "%4" FORMAT_OFF_T "M", (curl_off_t)(bytes/ONE_MEGABYTE));
91 /* 10000 MB - 8589934587 GB !! */
92 sprintf(max5, "%2d.%0dG",
93 (int)(bytes/ONE_GIGABYTE),
94 (int)(bytes%ONE_GIGABYTE)/(ONE_GIGABYTE/10) );
97 sprintf(max5, "%4" FORMAT_OFF_T "M", (curl_off_t)(bytes/ONE_MEGABYTE));
105 New proposed interface, 9th of February 2000:
107 pgrsStartNow() - sets start time
108 pgrsSetDownloadSize(x) - known expected download size
109 pgrsSetUploadSize(x) - known expected upload size
110 pgrsSetDownloadCounter() - amount of data currently downloaded
111 pgrsSetUploadCounter() - amount of data currently uploaded
112 pgrsUpdate() - show progress
113 pgrsDone() - transfer complete
117 void Curl_pgrsDone(struct connectdata *conn)
119 struct SessionHandle *data = conn->data;
120 data->progress.lastshow=0;
121 Curl_pgrsUpdate(conn); /* the final (forced) update */
122 if(!(data->progress.flags & PGRS_HIDE) &&
123 !data->progress.callback)
124 /* only output if we don't use a progress callback and we're not hidden */
125 fprintf(data->set.err, "\n");
128 /* reset all times except redirect */
129 void Curl_pgrsResetTimes(struct SessionHandle *data)
131 data->progress.t_nslookup = 0.0;
132 data->progress.t_connect = 0.0;
133 data->progress.t_pretransfer = 0.0;
134 data->progress.t_starttransfer = 0.0;
137 void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
144 case TIMER_STARTSINGLE:
145 /* This is set at the start of a single fetch */
146 data->progress.t_startsingle = Curl_tvnow();
149 case TIMER_NAMELOOKUP:
150 data->progress.t_nslookup =
151 Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
154 data->progress.t_connect =
155 Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
157 case TIMER_PRETRANSFER:
158 data->progress.t_pretransfer =
159 Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
161 case TIMER_STARTTRANSFER:
162 data->progress.t_starttransfer =
163 Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
165 case TIMER_POSTRANSFER:
166 /* this is the normal end-of-transfer thing */
169 data->progress.t_redirect =
170 Curl_tvdiff_secs(Curl_tvnow(), data->progress.start);
175 void Curl_pgrsStartNow(struct SessionHandle *data)
177 data->progress.speeder_c = 0; /* reset the progress meter display */
178 data->progress.start = Curl_tvnow();
181 void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, curl_off_t size)
183 data->progress.downloaded = size;
186 void Curl_pgrsSetUploadCounter(struct SessionHandle *data, curl_off_t size)
188 data->progress.uploaded = size;
191 void Curl_pgrsSetDownloadSize(struct SessionHandle *data, curl_off_t size)
193 data->progress.size_dl = size;
195 data->progress.flags |= PGRS_DL_SIZE_KNOWN;
197 data->progress.flags &= ~PGRS_DL_SIZE_KNOWN;
200 void Curl_pgrsSetUploadSize(struct SessionHandle *data, curl_off_t size)
202 data->progress.size_ul = size;
204 data->progress.flags |= PGRS_UL_SIZE_KNOWN;
206 data->progress.flags &= ~PGRS_UL_SIZE_KNOWN;
209 int Curl_pgrsUpdate(struct connectdata *conn)
217 curl_off_t total_transfer;
218 curl_off_t total_expected_transfer;
220 struct SessionHandle *data = conn->data;
221 int nowindex = data->progress.speeder_c% CURR_TIME;
223 int countindex; /* amount of seconds stored in the speeder array */
231 if(data->progress.flags & PGRS_HIDE)
232 ; /* We do enter this function even if we don't wanna see anything, since
233 this is were lots of the calculations are being made that will be used
234 even when not displayed! */
235 else if(!(data->progress.flags & PGRS_HEADERS_OUT)) {
236 if (!data->progress.callback) {
237 if(conn->resume_from)
238 fprintf(data->set.err,
239 "** Resuming transfer from byte position %" FORMAT_OFF_T
242 fprintf(data->set.err,
243 " %% Total %% Received %% Xferd Average Speed Time Time Time Current\n"
244 " Dload Upload Total Spent Left Speed\n");
246 data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */
249 now = Curl_tvnow(); /* what time is it */
251 /* The time spent so far (from the start) */
252 data->progress.timespent = Curl_tvdiff_secs(now, data->progress.start);
253 timespent = (long)data->progress.timespent*1000.0;
255 /* The average download speed this far */
256 data->progress.dlspeed =
257 data->progress.downloaded/(timespent?timespent:1);
259 /* The average upload speed this far */
260 data->progress.ulspeed =
261 data->progress.uploaded/(timespent?timespent:1);
263 if(data->progress.lastshow == Curl_tvlong(now))
264 return 0; /* never update this more than once a second if the end isn't
266 data->progress.lastshow = now.tv_sec;
268 /* Let's do the "current speed" thing, which should use the fastest
269 of the dl/ul speeds. Store the fasted speed at entry 'nowindex'. */
270 data->progress.speeder[ nowindex ] =
271 data->progress.downloaded>data->progress.uploaded?
272 data->progress.downloaded:data->progress.uploaded;
274 /* remember the exact time for this moment */
275 data->progress.speeder_time [ nowindex ] = now;
277 /* advance our speeder_c counter, which is increased every time we get
278 here and we expect it to never wrap as 2^32 is a lot of seconds! */
279 data->progress.speeder_c++;
281 /* figure out how many index entries of data we have stored in our speeder
282 array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of
283 transfer. Imagine, after one second we have filled in two entries,
284 after two seconds we've filled in three entries etc. */
285 countindex = ((data->progress.speeder_c>=CURR_TIME)?
286 CURR_TIME:data->progress.speeder_c) - 1;
288 /* first of all, we don't do this if there's no counted seconds yet */
292 /* Get the index position to compare with the 'nowindex' position.
293 Get the oldest entry possible. While we have less than CURR_TIME
294 entries, the first entry will remain the oldest. */
295 checkindex = (data->progress.speeder_c>=CURR_TIME)?
296 data->progress.speeder_c%CURR_TIME:0;
298 /* Figure out the exact time for the time span */
299 span_ms = Curl_tvdiff(now,
300 data->progress.speeder_time[checkindex]);
302 span_ms=1; /* at least one millisecond MUST have passed */
304 /* Calculate the average speed the last 'countindex' seconds */
305 data->progress.current_speed = (curl_off_t)
306 (data->progress.speeder[nowindex]-
307 data->progress.speeder[checkindex])/((double)span_ms/1000);
310 /* the first second we use the main average */
311 data->progress.current_speed =
312 (data->progress.ulspeed>data->progress.dlspeed)?
313 data->progress.ulspeed:data->progress.dlspeed;
315 if(data->progress.flags & PGRS_HIDE)
318 else if(data->set.fprogress) {
319 /* There's a callback set, so we call that instead of writing
320 anything ourselves. This really is the way to go. */
321 result= data->set.fprogress(data->set.progress_client,
322 (double)data->progress.size_dl,
323 (double)data->progress.downloaded,
324 (double)data->progress.size_ul,
325 (double)data->progress.uploaded);
327 failf(data, "Callback aborted");
331 /* Figure out the estimated time of arrival for the upload */
332 if((data->progress.flags & PGRS_UL_SIZE_KNOWN) &&
333 (data->progress.ulspeed > 0)) {
334 ulestimate = (long)(data->progress.size_ul / data->progress.ulspeed);
335 ulpercen = (long)(data->progress.uploaded / data->progress.size_ul)*100;
338 /* ... and the download */
339 if((data->progress.flags & PGRS_DL_SIZE_KNOWN) &&
340 (data->progress.dlspeed > 0)) {
341 dlestimate = (long)(data->progress.size_dl / data->progress.dlspeed);
342 dlpercen = (long)(data->progress.downloaded / data->progress.size_dl)*100;
345 /* Now figure out which of them that is slower and use for the for
347 total_estimate = ulestimate>dlestimate?ulestimate:dlestimate;
349 /* create the three time strings */
350 time2str(time_left, total_estimate > 0?(total_estimate - timespent):0);
351 time2str(time_total, total_estimate);
352 time2str(time_spent, timespent);
354 /* Get the total amount of data expected to get transfered */
355 total_expected_transfer =
356 (data->progress.flags & PGRS_UL_SIZE_KNOWN?
357 data->progress.size_ul:data->progress.uploaded)+
358 (data->progress.flags & PGRS_DL_SIZE_KNOWN?
359 data->progress.size_dl:data->progress.downloaded);
361 /* We have transfered this much so far */
362 total_transfer = data->progress.downloaded + data->progress.uploaded;
364 /* Get the percentage of data transfered so far */
365 if(total_expected_transfer > 0)
366 total_percen=(int)(total_transfer/total_expected_transfer)*100;
368 fprintf(data->set.err,
369 "\r%3d %s %3d %s %3d %s %s %s %s %s %s %s",
370 total_percen, /* 3 letters */ /* total % */
371 max5data(total_expected_transfer, max5[2]), /* total size */
372 dlpercen, /* 3 letters */ /* rcvd % */
373 max5data(data->progress.downloaded, max5[0]), /* rcvd size */
374 ulpercen, /* 3 letters */ /* xfer % */
375 max5data(data->progress.uploaded, max5[1]), /* xfer size */
376 max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */
377 max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */
378 time_total, /* 8 letters */ /* total time */
379 time_spent, /* 8 letters */ /* time spent */
380 time_left, /* 8 letters */ /* time left */
381 max5data(data->progress.current_speed, max5[5]) /* current speed */
384 /* we flush the output stream to make it appear as soon as possible */
385 fflush(data->set.err);