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