Update copyright year, since the file has been modified
[platform/upstream/curl.git] / lib / progress.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2006, 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 http://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  * $Id$
22  ***************************************************************************/
23
24 #include "setup.h"
25
26 #include <string.h>
27 #include <time.h>
28
29 #if defined(__EMX__)
30 #include <stdlib.h>
31 #endif
32
33 #include <curl/curl.h>
34 #include "urldata.h"
35 #include "sendf.h"
36 #include "progress.h"
37
38 #define _MPRINTF_REPLACE /* use our functions only */
39 #include <curl/mprintf.h>
40
41 /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
42    byte) */
43 static void time2str(char *r, long t)
44 {
45   long h;
46   if(!t) {
47     strcpy(r, "--:--:--");
48     return;
49   }
50   h = (t/3600);
51   if(h <= 99) {
52     long m = (t-(h*3600))/60;
53     long s = (t-(h*3600)-(m*60));
54     snprintf(r, 9, "%2ld:%02ld:%02ld",h,m,s);
55   }
56   else {
57     /* this equals to more than 99 hours, switch to a more suitable output
58        format to fit within the limits. */
59     if(h/24 <= 999)
60       snprintf(r, 9, "%3ldd %02ldh", h/24, h-(h/24)*24);
61     else
62       snprintf(r, 9, "%7ldd", h/24);
63   }
64 }
65
66 /* The point of this function would be to return a string of the input data,
67    but never longer than 5 columns (+ one zero byte).
68    Add suffix k, M, G when suitable... */
69 static char *max5data(curl_off_t bytes, char *max5)
70 {
71 #define ONE_KILOBYTE 1024
72 #define ONE_MEGABYTE (1024* ONE_KILOBYTE)
73 #define ONE_GIGABYTE (1024* ONE_MEGABYTE)
74 #define ONE_TERRABYTE ((curl_off_t)1024* ONE_GIGABYTE)
75 #define ONE_PETABYTE ((curl_off_t)1024* ONE_TERRABYTE)
76
77   if(bytes < 100000) {
78     snprintf(max5, 6, "%5" FORMAT_OFF_T, bytes);
79   }
80   else if(bytes < (10000*ONE_KILOBYTE)) {
81     snprintf(max5, 6, "%4" FORMAT_OFF_T "k", (curl_off_t)(bytes/ONE_KILOBYTE));
82   }
83   else if(bytes < (100*ONE_MEGABYTE)) {
84     /* 'XX.XM' is good as long as we're less than 100 megs */
85     snprintf(max5, 6, "%2d.%0dM",
86              (int)(bytes/ONE_MEGABYTE),
87              (int)(bytes%ONE_MEGABYTE)/(ONE_MEGABYTE/10) );
88   }
89 #if SIZEOF_CURL_OFF_T > 4
90   else if(bytes < ( (curl_off_t)10000*ONE_MEGABYTE))
91     /* 'XXXXM' is good until we're at 10000MB or above */
92     snprintf(max5, 6, "%4" FORMAT_OFF_T "M", (curl_off_t)(bytes/ONE_MEGABYTE));
93
94   else if(bytes < (curl_off_t)100*ONE_GIGABYTE)
95     /* 10000 MB - 100 GB, we show it as XX.XG */
96     snprintf(max5, 6, "%2d.%0dG",
97              (int)(bytes/ONE_GIGABYTE),
98              (int)(bytes%ONE_GIGABYTE)/(ONE_GIGABYTE/10) );
99
100   else if(bytes < (curl_off_t)10000 * ONE_GIGABYTE)
101     /* up to 10000GB, display without decimal: XXXXG */
102     snprintf(max5, 6, "%4dG", (int)(bytes/ONE_GIGABYTE));
103
104   else if(bytes < (curl_off_t)10000 * ONE_TERRABYTE)
105     /* up to 10000TB, display without decimal: XXXXT */
106     snprintf(max5, 6, "%4dT", (int)(bytes/ONE_TERRABYTE));
107   else {
108     /* up to 10000PB, display without decimal: XXXXP */
109     snprintf(max5, 6, "%4dP", (int)(bytes/ONE_PETABYTE));
110
111     /* 16384 petabytes (16 exabytes) is maximum a 64 bit number can hold,
112        but this type is signed so 8192PB will be max.*/
113   }
114
115 #else
116   else
117     snprintf(max5, 6, "%4" FORMAT_OFF_T "M", (curl_off_t)(bytes/ONE_MEGABYTE));
118 #endif
119
120   return max5;
121 }
122
123 /*
124
125    New proposed interface, 9th of February 2000:
126
127    pgrsStartNow() - sets start time
128    pgrsSetDownloadSize(x) - known expected download size
129    pgrsSetUploadSize(x) - known expected upload size
130    pgrsSetDownloadCounter() - amount of data currently downloaded
131    pgrsSetUploadCounter() - amount of data currently uploaded
132    pgrsUpdate() - show progress
133    pgrsDone() - transfer complete
134
135 */
136
137 void Curl_pgrsDone(struct connectdata *conn)
138 {
139   struct SessionHandle *data = conn->data;
140   data->progress.lastshow=0;
141   Curl_pgrsUpdate(conn); /* the final (forced) update */
142
143   data->progress.speeder_c = 0; /* reset the progress meter display */
144 }
145
146 /* reset all times except redirect */
147 void Curl_pgrsResetTimes(struct SessionHandle *data)
148 {
149   data->progress.t_nslookup = 0.0;
150   data->progress.t_connect = 0.0;
151   data->progress.t_pretransfer = 0.0;
152   data->progress.t_starttransfer = 0.0;
153 }
154
155 void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
156 {
157   switch(timer) {
158   default:
159   case TIMER_NONE:
160     /* mistake filter */
161     break;
162   case TIMER_STARTSINGLE:
163     /* This is set at the start of a single fetch */
164     data->progress.t_startsingle = Curl_tvnow();
165     break;
166
167   case TIMER_NAMELOOKUP:
168     data->progress.t_nslookup =
169       Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
170     break;
171   case TIMER_CONNECT:
172     data->progress.t_connect =
173       Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
174     break;
175   case TIMER_PRETRANSFER:
176     data->progress.t_pretransfer =
177       Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
178     break;
179   case TIMER_STARTTRANSFER:
180     data->progress.t_starttransfer =
181       Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
182     break;
183   case TIMER_POSTRANSFER:
184     /* this is the normal end-of-transfer thing */
185     break;
186   case TIMER_REDIRECT:
187     data->progress.t_redirect =
188       Curl_tvdiff_secs(Curl_tvnow(), data->progress.start);
189     break;
190   }
191 }
192
193 void Curl_pgrsStartNow(struct SessionHandle *data)
194 {
195   data->progress.speeder_c = 0; /* reset the progress meter display */
196   data->progress.start = Curl_tvnow();
197 }
198
199 void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, curl_off_t size)
200 {
201   data->progress.downloaded = size;
202 }
203
204 void Curl_pgrsSetUploadCounter(struct SessionHandle *data, curl_off_t size)
205 {
206   data->progress.uploaded = size;
207 }
208
209 void Curl_pgrsSetDownloadSize(struct SessionHandle *data, curl_off_t size)
210 {
211   data->progress.size_dl = size;
212   if(size > 0)
213     data->progress.flags |= PGRS_DL_SIZE_KNOWN;
214   else
215     data->progress.flags &= ~PGRS_DL_SIZE_KNOWN;
216 }
217
218 void Curl_pgrsSetUploadSize(struct SessionHandle *data, curl_off_t size)
219 {
220   data->progress.size_ul = size;
221   if(size > 0)
222     data->progress.flags |= PGRS_UL_SIZE_KNOWN;
223   else
224     data->progress.flags &= ~PGRS_UL_SIZE_KNOWN;
225 }
226
227 int Curl_pgrsUpdate(struct connectdata *conn)
228 {
229   struct timeval now;
230   int result;
231   char max5[6][10];
232   int dlpercen=0;
233   int ulpercen=0;
234   int total_percen=0;
235   curl_off_t total_transfer;
236   curl_off_t total_expected_transfer;
237   long timespent;
238   struct SessionHandle *data = conn->data;
239   int nowindex = data->progress.speeder_c% CURR_TIME;
240   int checkindex;
241   int countindex; /* amount of seconds stored in the speeder array */
242   char time_left[10];
243   char time_total[10];
244   char time_spent[10];
245   long ulestimate=0;
246   long dlestimate=0;
247   long total_estimate;
248
249   if(data->progress.flags & PGRS_HIDE)
250     ; /* We do enter this function even if we don't wanna see anything, since
251          this is were lots of the calculations are being made that will be used
252          even when not displayed! */
253   else if(!(data->progress.flags & PGRS_HEADERS_OUT)) {
254     if (!data->progress.callback) {
255       if(data->reqdata.resume_from)
256         fprintf(data->set.err,
257                 "** Resuming transfer from byte position %" FORMAT_OFF_T
258                 "\n",
259                 data->reqdata.resume_from);
260       fprintf(data->set.err,
261               "  %% Total    %% Received %% Xferd  Average Speed   Time    Time     Time  Current\n"
262               "                                 Dload  Upload   Total   Spent    Left  Speed\n");
263     }
264     data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */
265   }
266
267   now = Curl_tvnow(); /* what time is it */
268
269   /* The time spent so far (from the start) */
270   data->progress.timespent = Curl_tvdiff_secs(now, data->progress.start);
271   timespent = (long)data->progress.timespent;
272
273   /* The average download speed this far */
274   data->progress.dlspeed = (curl_off_t)
275     ((double)data->progress.downloaded/
276      (data->progress.timespent>0?data->progress.timespent:1));
277
278   /* The average upload speed this far */
279   data->progress.ulspeed = (curl_off_t)
280     ((double)data->progress.uploaded/
281      (data->progress.timespent>0?data->progress.timespent:1));
282
283   if(data->progress.lastshow == Curl_tvlong(now))
284     return 0; /* never update this more than once a second if the end isn't
285                  reached */
286   data->progress.lastshow = now.tv_sec;
287
288   /* Let's do the "current speed" thing, which should use the fastest
289      of the dl/ul speeds. Store the fasted speed at entry 'nowindex'. */
290   data->progress.speeder[ nowindex ] =
291     data->progress.downloaded>data->progress.uploaded?
292     data->progress.downloaded:data->progress.uploaded;
293
294   /* remember the exact time for this moment */
295   data->progress.speeder_time [ nowindex ] = now;
296
297   /* advance our speeder_c counter, which is increased every time we get
298      here and we expect it to never wrap as 2^32 is a lot of seconds! */
299   data->progress.speeder_c++;
300
301   /* figure out how many index entries of data we have stored in our speeder
302      array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of
303      transfer. Imagine, after one second we have filled in two entries,
304      after two seconds we've filled in three entries etc. */
305   countindex = ((data->progress.speeder_c>=CURR_TIME)?
306                 CURR_TIME:data->progress.speeder_c) - 1;
307
308   /* first of all, we don't do this if there's no counted seconds yet */
309   if(countindex) {
310     long span_ms;
311
312     /* Get the index position to compare with the 'nowindex' position.
313        Get the oldest entry possible. While we have less than CURR_TIME
314        entries, the first entry will remain the oldest. */
315     checkindex = (data->progress.speeder_c>=CURR_TIME)?
316       data->progress.speeder_c%CURR_TIME:0;
317
318     /* Figure out the exact time for the time span */
319     span_ms = Curl_tvdiff(now,
320                           data->progress.speeder_time[checkindex]);
321     if(0 == span_ms)
322       span_ms=1; /* at least one millisecond MUST have passed */
323
324     /* Calculate the average speed the last 'span_ms' milliseconds */
325     {
326       curl_off_t amount = data->progress.speeder[nowindex]-
327         data->progress.speeder[checkindex];
328
329       if(amount > 4294967 /* 0xffffffff/1000 */)
330         /* the 'amount' value is bigger than would fit in 32 bits if
331            multiplied with 1000, so we use the double math for this */
332         data->progress.current_speed = (curl_off_t)
333           ((double)amount/((double)span_ms/1000.0));
334       else
335         /* the 'amount' value is small enough to fit within 32 bits even
336            when multiplied with 1000 */
337         data->progress.current_speed = amount*1000/span_ms;
338     }
339   }
340   else
341     /* the first second we use the main average */
342     data->progress.current_speed =
343       (data->progress.ulspeed>data->progress.dlspeed)?
344       data->progress.ulspeed:data->progress.dlspeed;
345
346   if(data->progress.flags & PGRS_HIDE)
347     return 0;
348
349   else if(data->set.fprogress) {
350     /* There's a callback set, so we call that instead of writing
351        anything ourselves. This really is the way to go. */
352     result= data->set.fprogress(data->set.progress_client,
353                                 (double)data->progress.size_dl,
354                                 (double)data->progress.downloaded,
355                                 (double)data->progress.size_ul,
356                                 (double)data->progress.uploaded);
357     if(result)
358       failf(data, "Callback aborted");
359     return result;
360   }
361
362   /* Figure out the estimated time of arrival for the upload */
363   if((data->progress.flags & PGRS_UL_SIZE_KNOWN) &&
364      (data->progress.ulspeed>0) &&
365      (data->progress.size_ul > 100) ) {
366     ulestimate = (long)(data->progress.size_ul / data->progress.ulspeed);
367     ulpercen = (int)(100*(data->progress.uploaded/100) /
368                       (data->progress.size_ul/100) );
369   }
370
371   /* ... and the download */
372   if((data->progress.flags & PGRS_DL_SIZE_KNOWN) &&
373      (data->progress.dlspeed>0) &&
374      (data->progress.size_dl>100)) {
375     dlestimate = (long)(data->progress.size_dl / data->progress.dlspeed);
376     dlpercen = (int)(100*(data->progress.downloaded/100) /
377                       (data->progress.size_dl/100));
378   }
379
380   /* Now figure out which of them that is slower and use for the for
381      total estimate! */
382   total_estimate = ulestimate>dlestimate?ulestimate:dlestimate;
383
384   /* create the three time strings */
385   time2str(time_left, total_estimate > 0?(total_estimate - timespent):0);
386   time2str(time_total, total_estimate);
387   time2str(time_spent, timespent);
388
389   /* Get the total amount of data expected to get transfered */
390   total_expected_transfer =
391     (data->progress.flags & PGRS_UL_SIZE_KNOWN?
392      data->progress.size_ul:data->progress.uploaded)+
393     (data->progress.flags & PGRS_DL_SIZE_KNOWN?
394      data->progress.size_dl:data->progress.downloaded);
395
396   /* We have transfered this much so far */
397   total_transfer = data->progress.downloaded + data->progress.uploaded;
398
399   /* Get the percentage of data transfered so far */
400   if(total_expected_transfer > 100)
401     total_percen=(int)(100*(total_transfer/100) /
402                        (total_expected_transfer/100) );
403
404   fprintf(data->set.err,
405           "\r%3d %s  %3d %s  %3d %s  %s  %s %s %s %s %s",
406           total_percen,  /* 3 letters */                /* total % */
407           max5data(total_expected_transfer, max5[2]),   /* total size */
408           dlpercen,      /* 3 letters */                /* rcvd % */
409           max5data(data->progress.downloaded, max5[0]), /* rcvd size */
410           ulpercen,      /* 3 letters */                /* xfer % */
411           max5data(data->progress.uploaded, max5[1]),   /* xfer size */
412           max5data(data->progress.dlspeed, max5[3]),    /* avrg dl speed */
413           max5data(data->progress.ulspeed, max5[4]),    /* avrg ul speed */
414           time_total,    /* 8 letters */                /* total time */
415           time_spent,    /* 8 letters */                /* time spent */
416           time_left,     /* 8 letters */                /* time left */
417           max5data(data->progress.current_speed, max5[5]) /* current speed */
418           );
419
420   /* we flush the output stream to make it appear as soon as possible */
421   fflush(data->set.err);
422
423   return 0;
424 }