Dirk Manske's feedback:
[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 #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     sprintf(r, "%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       sprintf(r, "%3ldd %02ldh", h/24, h-(h/24)*24);
61     else
62       sprintf(r, "%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. Add suffix k, M, G when suitable... */
68 static char *max5data(curl_off_t bytes, char *max5)
69 {
70 #define ONE_KILOBYTE 1024
71 #define ONE_MEGABYTE (1024*1024)
72 #define ONE_GIGABYTE (1024*1024*1024)
73
74   if(bytes < 100000) {
75     sprintf(max5, "%5" FORMAT_OFF_T, bytes);
76   }
77   else if(bytes < (10000*ONE_KILOBYTE)) {
78     sprintf(max5, "%4" FORMAT_OFF_T "k", (curl_off_t)(bytes/ONE_KILOBYTE));
79   }
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) );
85   }
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));
89   }
90   else
91     /* 10000 MB - 8589934587 GB !! */
92     sprintf(max5, "%2d.%0dG",
93             (int)(bytes/ONE_GIGABYTE),
94             (int)(bytes%ONE_GIGABYTE)/(ONE_GIGABYTE/10) );
95 #else
96   else
97     sprintf(max5, "%4" FORMAT_OFF_T "M", (curl_off_t)(bytes/ONE_MEGABYTE));
98 #endif
99
100   return max5;
101 }
102
103 /* 
104
105    New proposed interface, 9th of February 2000:
106
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
114
115 */
116
117 void Curl_pgrsDone(struct connectdata *conn)
118 {
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");
126 }
127
128 /* reset all times except redirect */
129 void Curl_pgrsResetTimes(struct SessionHandle *data)
130 {
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;
135 }
136
137 void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
138 {
139   switch(timer) {
140   default:
141   case TIMER_NONE:
142     /* mistake filter */
143     break;
144   case TIMER_STARTSINGLE:
145     /* This is set at the start of a single fetch */
146     data->progress.t_startsingle = Curl_tvnow();
147     break;
148
149   case TIMER_NAMELOOKUP:
150     data->progress.t_nslookup =
151       (double)Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000.0;
152     break;
153   case TIMER_CONNECT:
154     data->progress.t_connect =
155       (double)Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000.0;
156     break;
157   case TIMER_PRETRANSFER:
158     data->progress.t_pretransfer =
159       (double)Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000.0;
160     break;
161   case TIMER_STARTTRANSFER:
162     data->progress.t_starttransfer =
163       (double)Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle)/1000.0;
164     break;
165   case TIMER_POSTRANSFER:
166     /* this is the normal end-of-transfer thing */
167     break;
168   case TIMER_REDIRECT:
169     data->progress.t_redirect =
170       (double)Curl_tvdiff(Curl_tvnow(), data->progress.start)/1000.0;
171     break;
172   }
173 }
174
175 void Curl_pgrsStartNow(struct SessionHandle *data)
176 {
177   data->progress.speeder_c = 0; /* reset the progress meter display */
178   data->progress.start = Curl_tvnow();
179 }
180
181 void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, curl_off_t size)
182 {
183   data->progress.downloaded = size;
184 }
185
186 void Curl_pgrsSetUploadCounter(struct SessionHandle *data, curl_off_t size)
187 {
188   data->progress.uploaded = size;
189 }
190
191 void Curl_pgrsSetDownloadSize(struct SessionHandle *data, curl_off_t size)
192 {
193   data->progress.size_dl = size;
194   if(size > 0)
195     data->progress.flags |= PGRS_DL_SIZE_KNOWN;
196   else
197     data->progress.flags &= ~PGRS_DL_SIZE_KNOWN;
198 }
199
200 void Curl_pgrsSetUploadSize(struct SessionHandle *data, curl_off_t size)
201 {
202   data->progress.size_ul = size;
203   if(size > 0)
204     data->progress.flags |= PGRS_UL_SIZE_KNOWN;
205   else
206     data->progress.flags &= ~PGRS_UL_SIZE_KNOWN;
207 }
208
209 int Curl_pgrsUpdate(struct connectdata *conn)
210 {
211   struct timeval now;
212   int result;
213   char max5[6][10];
214   int dlpercen=0;
215   int ulpercen=0;
216   int total_percen=0;
217   curl_off_t total_transfer;
218   curl_off_t total_expected_transfer;
219   long timespent;
220   struct SessionHandle *data = conn->data;
221   int nowindex = data->progress.speeder_c% CURR_TIME;
222   int checkindex;
223   int countindex; /* amount of seconds stored in the speeder array */
224   char time_left[10];
225   char time_total[10];
226   char time_spent[10];
227   long ulestimate=0;
228   long dlestimate=0;
229   long total_estimate;
230
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
240                 "\n",
241                 conn->resume_from);
242       fprintf(data->set.err,
243               "  %% Total    %% Received %% Xferd  Average Speed   Time    Time     Time  Current\n"
244               "                                 Dload  Upload   Total   Spent    Left  Speed\n");
245     }
246     data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */
247   }
248
249   now = Curl_tvnow(); /* what time is it */
250
251   /* The time spent so far (from the start) */
252   data->progress.timespent = Curl_tvdiff(now, data->progress.start)/1000.0;
253   timespent = (long)data->progress.timespent;
254
255   /* The average download speed this far */
256   data->progress.dlspeed =
257     data->progress.downloaded/(timespent?timespent:1);
258
259   /* The average upload speed this far */
260   data->progress.ulspeed =
261     data->progress.uploaded/(timespent?timespent:1);
262
263   if(data->progress.lastshow == Curl_tvlong(now))
264     return 0; /* never update this more than once a second if the end isn't 
265                  reached */
266   data->progress.lastshow = now.tv_sec;
267
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;
273
274   /* remember the exact time for this moment */
275   data->progress.speeder_time [ nowindex ] = now;
276
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++;
280
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;
287
288   /* first of all, we don't do this if there's no counted seconds yet */
289   if(countindex) {
290     long span_ms;
291
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;
297
298     /* Figure out the exact time for the time span */
299     span_ms = Curl_tvdiff(now,
300                           data->progress.speeder_time[checkindex]);
301     if(0 == span_ms)
302       span_ms=1; /* at least one millisecond MUST have passed */
303
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);
308   }
309   else
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;
314
315   if(data->progress.flags & PGRS_HIDE)
316     return 0;
317
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);
326     if(result)
327       failf(data, "Callback aborted");
328     return result;
329   }
330
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;
336   }
337
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;
343   }
344     
345   /* Now figure out which of them that is slower and use for the for
346      total estimate! */
347   total_estimate = ulestimate>dlestimate?ulestimate:dlestimate;
348
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);
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 > 0)
366     total_percen=(int)(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           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 */
382           );
383
384   /* we flush the output stream to make it appear as soon as possible */
385   fflush(data->set.err);
386
387   return 0;
388 }