progress: Make default UTF-8 progress bar less black.
[libguestfs.git] / fish / progress.c
1 /* libguestfs - mini library for progress bars.
2  * Copyright (C) 2010-2011 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <math.h>
26 #include <sys/time.h>
27 #include <langinfo.h>
28
29 #include "progress.h"
30
31 /* Include these last since they redefine symbols such as 'lines'
32  * which seriously breaks other headers.
33  */
34 #include <term.h>
35 #include <curses.h>
36
37 /* Provided by termcap or terminfo emulation, but not defined
38  * in any header file.
39  */
40 extern const char *UP;
41
42 #define STREQ(a,b) (strcmp((a),(b)) == 0)
43
44 /* Compute the running mean and standard deviation from the
45  * series of estimated values.
46  *
47  * Method:
48  * http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
49  * Checked in a test program against answers given by Wolfram Alpha.
50  */
51 struct rmsd {
52   double a;                     /* mean */
53   double i;                     /* number of samples */
54   double q;
55 };
56
57 static void
58 rmsd_init (struct rmsd *r)
59 {
60   r->a = 0;
61   r->i = 1;
62   r->q = 0;
63 }
64
65 static void
66 rmsd_add_sample (struct rmsd *r, double x)
67 {
68   double a_next, q_next;
69
70   a_next = r->a + (x - r->a) / r->i;
71   q_next = r->q + (x - r->a) * (x - a_next);
72   r->a = a_next;
73   r->q = q_next;
74   r->i += 1.0;
75 }
76
77 static double
78 rmsd_get_mean (const struct rmsd *r)
79 {
80   return r->a;
81 }
82
83 static double
84 rmsd_get_standard_deviation (const struct rmsd *r)
85 {
86   return sqrt (r->q / (r->i - 1.0));
87 }
88
89 struct progress_bar {
90   double start;         /* start time of command */
91   int count;            /* number of progress notifications per cmd */
92   struct rmsd rmsd;     /* running mean and standard deviation */
93   int have_terminfo;
94   int utf8_mode;
95   int machine_readable;
96 };
97
98 struct progress_bar *
99 progress_bar_init (unsigned flags)
100 {
101   struct progress_bar *bar;
102   char *term;
103
104   bar = malloc (sizeof *bar);
105   if (bar == NULL)
106     return NULL;
107
108   if (flags & PROGRESS_BAR_MACHINE_READABLE) {
109     bar->machine_readable = 1;
110     bar->utf8_mode = 0;
111     bar->have_terminfo = 0;
112   } else {
113     bar->machine_readable = 0;
114
115     bar->utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8");
116
117     bar->have_terminfo = 0;
118
119     term = getenv ("TERM");
120     if (term) {
121       if (tgetent (NULL, term) == 1)
122         bar->have_terminfo = 1;
123     }
124   }
125
126   /* Call this to ensure the other fields are in a reasonable state.
127    * It is still the caller's responsibility to reset the progress bar
128    * before each command.
129    */
130   progress_bar_reset (bar);
131
132   return bar;
133 }
134
135 void
136 progress_bar_free (struct progress_bar *bar)
137 {
138   free (bar);
139 }
140
141 /* This function is called just before we issue any command. */
142 void
143 progress_bar_reset (struct progress_bar *bar)
144 {
145   /* The time at which this command was issued. */
146   struct timeval start_t;
147   gettimeofday (&start_t, NULL);
148
149   bar->start = start_t.tv_sec + start_t.tv_usec / 1000000.;
150
151   bar->count = 0;
152
153   rmsd_init (&bar->rmsd);
154 }
155
156 static const char *
157 spinner (struct progress_bar *bar, int count)
158 {
159   /* Choice of unicode spinners.
160    *
161    * For basic dingbats, see:
162    * http://www.fileformat.info/info/unicode/block/geometric_shapes/utf8test.htm
163    * http://www.fileformat.info/info/unicode/block/dingbats/utf8test.htm
164    *
165    * Arrows are a mess in unicode.  This page helps a lot:
166    * http://xahlee.org/comp/unicode_arrows.html
167    *
168    * I prefer something which doesn't point, just spins.
169    */
170   /* Black pointing triangle. */
171   //static const char *us[] = { "\u25b2", "\u25b6", "\u25bc", "\u25c0" };
172   /* White pointing triangle. */
173   //static const char *us[] = { "\u25b3", "\u25b7", "\u25bd", "\u25c1" };
174   /* Circle with half black. */
175   static const char *us[] = { "\u25d0", "\u25d3", "\u25d1", "\u25d2" };
176   /* White square white quadrant. */
177   //static const char *us[] = { "\u25f0", "\u25f3", "\u25f2", "\u25f1" };
178   /* White circle white quadrant. */
179   //static const char *us[] = { "\u25f4", "\u25f7", "\u25f6", "\u25f5" };
180   /* Black triangle. */
181   //static const char *us[] = { "\u25e2", "\u25e3", "\u25e4", "\u25e5" };
182   /* Spinning arrow in 8 directions. */
183   //static const char *us[] = { "\u2190", "\u2196", "\u2191", "\u2197",
184   //                            "\u2192", "\u2198", "\u2193", "\u2199" };
185
186   /* ASCII spinner. */
187   static const char *as[] = { "/", "-", "\\", "|" };
188
189   const char **s;
190   size_t n;
191
192   if (bar->utf8_mode) {
193     s = us;
194     n = sizeof us / sizeof us[0];
195   }
196   else {
197     s = as;
198     n = sizeof as / sizeof as[0];
199   }
200
201   return s[count % n];
202 }
203
204 /* Return remaining time estimate (in seconds) for current call.
205  *
206  * This returns the running mean estimate of remaining time, but if
207  * the latest estimate of total time is greater than two s.d.'s from
208  * the running mean then we don't print anything because we're not
209  * confident that the estimate is meaningful.  (Returned value is <0.0
210  * when nothing should be printed).
211  */
212 static double
213 estimate_remaining_time (struct progress_bar *bar, double ratio)
214 {
215   if (ratio <= 0.)
216     return -1.0;
217
218   struct timeval now_t;
219   gettimeofday (&now_t, NULL);
220
221   double now = now_t.tv_sec + now_t.tv_usec / 1000000.;
222   /* We've done 'ratio' of the work in 'now - start' seconds. */
223   double time_passed = now - bar->start;
224
225   double total_time = time_passed / ratio;
226
227   /* Add total_time to running mean and s.d. and then see if our
228    * estimate of total time is meaningful.
229    */
230   rmsd_add_sample (&bar->rmsd, total_time);
231
232   double mean = rmsd_get_mean (&bar->rmsd);
233   double sd = rmsd_get_standard_deviation (&bar->rmsd);
234   if (fabs (total_time - mean) >= 2.0*sd)
235     return -1.0;
236
237   /* Don't return early estimates. */
238   if (time_passed < 3.0)
239     return -1.0;
240
241   return total_time - time_passed;
242 }
243
244 /* The overhead is how much we subtract before we get to the progress
245  * bar itself.
246  *
247  * / 100% [########---------------] xx:xx
248  * | |    |                       | |
249  * | |    |                       | time (5 cols)
250  * | |    |                       |
251  * | |    open paren + close paren + space (3 cols)
252  * | |
253  * | percentage and space (5 cols)
254  * |
255  * spinner and space (2 cols)
256  *
257  * Total = 2 + 5 + 3 + 5 = 15
258  */
259 #define COLS_OVERHEAD 15
260
261 void
262 progress_bar_set (struct progress_bar *bar,
263                   uint64_t position, uint64_t total)
264 {
265   int i, cols, pulse_mode;
266   double ratio;
267   const char *s_open, *s_dot, *s_dash, *s_close;
268
269   if (bar->machine_readable || bar->have_terminfo == 0) {
270   dumb:
271     printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
272   } else {
273     cols = tgetnum ((char *) "co");
274     if (cols < 32) goto dumb;
275
276     /* Update an existing progress bar just printed? */
277     if (bar->count > 0)
278       tputs (UP, 2, putchar);
279     bar->count++;
280
281     /* Find out if we're in "pulse mode". */
282     pulse_mode = position == 0 && total == 1;
283
284     ratio = (double) position / total;
285     if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1;
286
287     if (pulse_mode) {
288       printf ("%s --- ", spinner (bar, bar->count));
289     }
290     else if (ratio < 1) {
291       int percent = 100.0 * ratio;
292       printf ("%s%3d%% ", spinner (bar, bar->count), percent);
293     }
294     else {
295       fputs (" 100% ", stdout);
296     }
297
298     if (bar->utf8_mode) {
299       s_open = "\u27e6";
300       s_dot = "\u2593";
301       s_dash = "\u2550";
302       s_close = "\u27e7";
303     } else {
304       s_open = "["; s_dot = "#"; s_dash = "-"; s_close = "]";
305     }
306
307     fputs (s_open, stdout);
308
309     if (!pulse_mode) {
310       int dots = ratio * (double) (cols - COLS_OVERHEAD);
311
312       for (i = 0; i < dots; ++i)
313         fputs (s_dot, stdout);
314       for (i = dots; i < cols - COLS_OVERHEAD; ++i)
315         fputs (s_dash, stdout);
316     }
317     else {           /* "Pulse mode": the progress bar just pulses. */
318       for (i = 0; i < cols - COLS_OVERHEAD; ++i) {
319         int cc = (bar->count * 3 - i) % (cols - COLS_OVERHEAD);
320         if (cc >= 0 && cc <= 3)
321           fputs (s_dot, stdout);
322         else
323           fputs (s_dash, stdout);
324       }
325     }
326
327     fputs (s_close, stdout);
328     fputc (' ', stdout);
329
330     /* Time estimate. */
331     double estimate = estimate_remaining_time (bar, ratio);
332     if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) {
333       /* Display hours<h> */
334       estimate /= 60. * 60.;
335       int hh = floor (estimate);
336       printf (">%dh", hh);
337     } else if (estimate >= 100.0 * 60.0 /* >= 100 minutes */) {
338       /* Display hours<h>minutes */
339       estimate /= 60. * 60.;
340       int hh = floor (estimate);
341       double ignore;
342       int mm = floor (modf (estimate, &ignore) * 60.);
343       printf ("%02dh%02d", hh, mm);
344     } else if (estimate >= 0.0) {
345       /* Display minutes:seconds */
346       estimate /= 60.;
347       int mm = floor (estimate);
348       double ignore;
349       int ss = floor (modf (estimate, &ignore) * 60.);
350       printf ("%02d:%02d", mm, ss);
351     }
352     else /* < 0 means estimate was not meaningful */
353       fputs ("--:--", stdout);
354
355     fputc ('\n', stdout);
356   }
357 }