gpg: Extend the PROGRESS line to give the used unit.
[gnupg.git] / g10 / progress.c
1 /* progress.c - emit progress status lines
2  * Copyright (C) 2003, 2006 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22
23 #include "gpg.h"
24 #include "iobuf.h"
25 #include "filter.h"
26 #include "status.h"
27 #include "util.h"
28 #include "options.h"
29
30 /* Create a new context for use with the progress filter.  We need to
31    allocate such contexts on the heap because there is no guarantee
32    that at the end of a function the filter has already been popped
33    off.  In general this will happen but with malformed packets it is
34    possible that a filter has not yet reached the end-of-stream when
35    the function has done all processing.  Checking in each function
36    that end-of-stream has been reached would be to cumbersome.
37
38    What we also do is to shortcut the progress handler by having this
39    function return NULL if progress information has not been
40    requested.
41 */
42 progress_filter_context_t *
43 new_progress_context (void)
44 {
45   progress_filter_context_t *pfx;
46
47   if (!opt.enable_progress_filter)
48     return NULL;
49
50   if (!is_status_enabled ())
51     return NULL;
52
53   pfx = xcalloc (1, sizeof *pfx);
54   pfx->refcount = 1;
55
56   return pfx;
57 }
58
59 /* Release a progress filter context.  Passing NULL is explicitly
60    allowed and a no-op.  */
61 void
62 release_progress_context (progress_filter_context_t *pfx)
63 {
64   if (!pfx)
65     return;
66   log_assert (pfx->refcount);
67   if ( --pfx->refcount )
68     return;
69   xfree (pfx->what);
70   xfree (pfx);
71 }
72
73
74 static void
75 write_status_progress (const char *what,
76                        unsigned long current, unsigned long total)
77 {
78   char buffer[60];
79   char units[] = "BKMGTPEZY?";
80   int unitidx = 0;
81
82   /* Although we use an unsigned long for the values, 32 bit
83    * applications using GPGME will use an "int" and thus are limited
84    * in the total size which can be represented.  On Windows, where
85    * sizeof(int)==sizeof(long), this is even worse and will lead to an
86    * integer overflow for all files larger than 2 GiB.  Although, the
87    * allowed value range of TOTAL and CURRENT is nowhere specified, we
88    * better protect applications from the need to handle negative
89    * values.  The common usage pattern of the progress information is
90    * to display how many percent of the operation has been done and
91    * thus scaling CURRENT and TOTAL down before they get to large,
92    * should not have a noticeable effect except for rounding
93    * imprecision. */
94   if (total)
95     {
96       if (current > total)
97         current = total;
98
99       while (total > 1024*1024)
100         {
101           total /= 1024;
102           current /= 1024;
103           unitidx++;
104         }
105     }
106   else
107     {
108       while (current > 1024*1024)
109         {
110           current /= 1024;
111           unitidx++;
112         }
113     }
114
115   if (unitidx > 9)
116     unitidx = 9;
117
118   snprintf (buffer, sizeof buffer, "%.20s ? %lu %lu %c%s",
119             what? what : "?", current, total,
120             units[unitidx],
121             unitidx? "iB" : "");
122   write_status_text (STATUS_PROGRESS, buffer);
123 }
124
125
126 /****************
127  * The filter is used to report progress to the user.
128  */
129 static int
130 progress_filter (void *opaque, int control,
131                  IOBUF a, byte *buf, size_t *ret_len)
132 {
133   int rc = 0;
134   progress_filter_context_t *pfx = opaque;
135
136   if (control == IOBUFCTRL_INIT)
137     {
138       pfx->last = 0;
139       pfx->offset = 0;
140       pfx->last_time = make_timestamp ();
141
142       write_status_progress (pfx->what, pfx->offset, pfx->total);
143     }
144   else if (control == IOBUFCTRL_UNDERFLOW)
145     {
146       u32 timestamp = make_timestamp ();
147       int len = iobuf_read (a, buf, *ret_len);
148
149       if (len >= 0)
150         {
151           pfx->offset += len;
152           *ret_len = len;
153         }
154       else
155         {
156           *ret_len = 0;
157           rc = -1;
158         }
159       if ((len == -1 && pfx->offset != pfx->last)
160           || timestamp - pfx->last_time > 0)
161         {
162           write_status_progress (pfx->what, pfx->offset, pfx->total);
163           pfx->last = pfx->offset;
164           pfx->last_time = timestamp;
165         }
166     }
167   else if (control == IOBUFCTRL_FREE)
168     {
169       release_progress_context (pfx);
170     }
171   else if (control == IOBUFCTRL_DESC)
172     mem2str (buf, "progress_filter", *ret_len);
173   return rc;
174 }
175
176 void
177 handle_progress (progress_filter_context_t *pfx, IOBUF inp, const char *name)
178 {
179   off_t filesize = 0;
180
181   if (!pfx)
182     return;
183
184   log_assert (opt.enable_progress_filter);
185   log_assert (is_status_enabled ());
186
187   if ( !iobuf_is_pipe_filename (name) && *name )
188     filesize = iobuf_get_filelength (inp, NULL);
189   else if (opt.set_filesize)
190     filesize = opt.set_filesize;
191
192   /* register the progress filter */
193   pfx->what = xstrdup (name ? name : "stdin");
194   pfx->total = filesize;
195   pfx->refcount++;
196   iobuf_push_filter (inp, progress_filter, pfx);
197 }