David J Meyer's large file support.
[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,
232                 "** Resuming transfer from byte position %Od\n",
233                 conn->resume_from);
234       fprintf(data->set.err,
235               "  %% Total    %% Received %% Xferd  Average Speed          Time             Curr.\n"
236               "                                 Dload  Upload Total    Current  Left    Speed\n");
237     }
238     data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */
239   }
240
241   now = Curl_tvnow(); /* what time is it */
242
243   /* The exact time spent so far (from the start) */
244   timespent = (double)Curl_tvdiff (now, data->progress.start)/1000;
245
246   data->progress.timespent = timespent;
247
248   /* The average download speed this far */
249   data->progress.dlspeed =
250     data->progress.downloaded/(timespent>0.01?timespent:1);
251
252   /* The average upload speed this far */
253   data->progress.ulspeed =
254     data->progress.uploaded/(timespent>0.01?timespent:1);
255
256   if(data->progress.lastshow == Curl_tvlong(now))
257     return 0; /* never update this more than once a second if the end isn't 
258                  reached */
259   data->progress.lastshow = now.tv_sec;
260
261   /* Let's do the "current speed" thing, which should use the fastest
262      of the dl/ul speeds. Store the fasted speed at entry 'nowindex'. */
263   data->progress.speeder[ nowindex ] =
264     data->progress.downloaded>data->progress.uploaded?
265     data->progress.downloaded:data->progress.uploaded;
266
267   /* remember the exact time for this moment */
268   data->progress.speeder_time [ nowindex ] = now;
269
270   /* advance our speeder_c counter, which is increased every time we get
271      here and we expect it to never wrap as 2^32 is a lot of seconds! */
272   data->progress.speeder_c++;
273
274   /* figure out how many index entries of data we have stored in our speeder
275      array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of
276      transfer. Imagine, after one second we have filled in two entries,
277      after two seconds we've filled in three entries etc. */
278   countindex = ((data->progress.speeder_c>=CURR_TIME)?
279                 CURR_TIME:data->progress.speeder_c) - 1;
280
281   /* first of all, we don't do this if there's no counted seconds yet */
282   if(countindex) {
283     long span_ms;
284
285     /* Get the index position to compare with the 'nowindex' position.
286        Get the oldest entry possible. While we have less than CURR_TIME
287        entries, the first entry will remain the oldest. */
288     checkindex = (data->progress.speeder_c>=CURR_TIME)?
289       data->progress.speeder_c%CURR_TIME:0;
290
291     /* Figure out the exact time for the time span */
292     span_ms = Curl_tvdiff(now,
293                           data->progress.speeder_time[checkindex]);
294     if(0 == span_ms)
295       span_ms=1; /* at least one millisecond MUST have passed */
296
297     /* Calculate the average speed the last 'countindex' seconds */
298     data->progress.current_speed =
299       (data->progress.speeder[nowindex]-
300        data->progress.speeder[checkindex])/((double)span_ms/1000);
301   }
302   else
303     /* the first second we use the main average */
304     data->progress.current_speed =
305       (data->progress.ulspeed>data->progress.dlspeed)?
306       data->progress.ulspeed:data->progress.dlspeed;
307
308   if(data->progress.flags & PGRS_HIDE)
309     return 0;
310
311   else if(data->set.fprogress) {
312     /* There's a callback set, so we call that instead of writing
313        anything ourselves. This really is the way to go. */
314     result= data->set.fprogress(data->set.progress_client,
315                                 data->progress.size_dl,
316                                 data->progress.downloaded,
317                                 data->progress.size_ul,
318                                 data->progress.uploaded);
319     if(result)
320       failf(data, "Callback aborted");
321     return result;
322   }
323
324   /* Figure out the estimated time of arrival for the upload */
325   if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && data->progress.ulspeed){
326     ulestimate = data->progress.size_ul / data->progress.ulspeed;
327     ulpercen = (data->progress.uploaded / data->progress.size_ul)*100;
328   }
329
330   /* ... and the download */
331   if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && data->progress.dlspeed) {
332     dlestimate = data->progress.size_dl / data->progress.dlspeed;
333     dlpercen = (data->progress.downloaded / data->progress.size_dl)*100;
334   }
335     
336   /* Now figure out which of them that is slower and use for the for
337      total estimate! */
338   total_estimate = ulestimate>dlestimate?ulestimate:dlestimate;
339
340
341   /* If we have a total estimate, we can display that and the expected
342      time left */
343   if(total_estimate) {
344     time2str(time_left, (int)(total_estimate - data->progress.timespent)); 
345     time2str(time_total, (int)total_estimate);
346   }
347   else {
348     /* otherwise we blank those times */
349     strcpy(time_left,  "--:--:--");
350     strcpy(time_total, "--:--:--");
351   }
352   /* The time spent so far is always known */
353   time2str(time_current, (int)data->progress.timespent);
354
355   /* Get the total amount of data expected to get transfered */
356   total_expected_transfer = 
357     (data->progress.flags & PGRS_UL_SIZE_KNOWN?
358      data->progress.size_ul:data->progress.uploaded)+
359     (data->progress.flags & PGRS_DL_SIZE_KNOWN?
360      data->progress.size_dl:data->progress.downloaded);
361       
362   /* We have transfered this much so far */
363   total_transfer = data->progress.downloaded + data->progress.uploaded;
364
365   /* Get the percentage of data transfered so far */
366   if(total_expected_transfer)
367     total_percen=(double)(total_transfer/total_expected_transfer)*100;
368
369   fprintf(data->set.err,
370           "\r%3d %s  %3d %s  %3d %s  %s  %s %s %s %s %s",
371           (int)total_percen,                            /* total % */
372           max5data(total_expected_transfer, max5[2]),   /* total size */
373           (int)dlpercen,                                /* rcvd % */
374           max5data(data->progress.downloaded, max5[0]), /* rcvd size */
375           (int)ulpercen,                                /* xfer % */
376           max5data(data->progress.uploaded, max5[1]),   /* xfer size */
377
378           max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */
379           max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */
380           time_total,                           /* total time */
381           time_current,                         /* current time */
382           time_left,                            /* time left */
383           max5data(data->progress.current_speed, max5[5]) /* current speed */
384           );
385
386   /* we flush the output stream to make it appear as soon as possible */
387   fflush(data->set.err);
388
389   return 0;
390 }