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