fish: Add 'pulse mode' to the progress bar.
[libguestfs.git] / fish / progress.c
1 /* guestfish - the filesystem interactive shell
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 <inttypes.h>
24 #include <math.h>
25 #include <sys/time.h>
26
27 #include <guestfs.h>
28
29 #include "fish.h"
30 #include "rmsd.h"
31
32 /* Include these last since they redefine symbols such as 'lines'
33  * which seriously breaks other headers.
34  */
35 #include <term.h>
36 #include <curses.h>
37
38 /* Provided by termcap or terminfo emulation, but not defined
39  * in any header file.
40  */
41 extern const char *UP;
42
43 static const char *
44 spinner (int count)
45 {
46   /* Choice of unicode spinners.
47    *
48    * For basic dingbats, see:
49    * http://www.fileformat.info/info/unicode/block/geometric_shapes/utf8test.htm
50    * http://www.fileformat.info/info/unicode/block/dingbats/utf8test.htm
51    *
52    * Arrows are a mess in unicode.  This page helps a lot:
53    * http://xahlee.org/comp/unicode_arrows.html
54    *
55    * I prefer something which doesn't point, just spins.
56    */
57   /* Black pointing triangle. */
58   //static const char *us[] = { "\u25b2", "\u25b6", "\u25bc", "\u25c0" };
59   /* White pointing triangle. */
60   //static const char *us[] = { "\u25b3", "\u25b7", "\u25bd", "\u25c1" };
61   /* Circle with half black. */
62   static const char *us[] = { "\u25d0", "\u25d3", "\u25d1", "\u25d2" };
63   /* White square white quadrant. */
64   //static const char *us[] = { "\u25f0", "\u25f3", "\u25f2", "\u25f1" };
65   /* White circle white quadrant. */
66   //static const char *us[] = { "\u25f4", "\u25f7", "\u25f6", "\u25f5" };
67   /* Black triangle. */
68   //static const char *us[] = { "\u25e2", "\u25e3", "\u25e4", "\u25e5" };
69   /* Spinning arrow in 8 directions. */
70   //static const char *us[] = { "\u2190", "\u2196", "\u2191", "\u2197",
71   //                            "\u2192", "\u2198", "\u2193", "\u2199" };
72
73   /* ASCII spinner. */
74   static const char *as[] = { "/", "-", "\\", "|" };
75
76   const char **s;
77   size_t n;
78
79   if (utf8_mode) {
80     s = us;
81     n = sizeof us / sizeof us[0];
82   }
83   else {
84     s = as;
85     n = sizeof as / sizeof as[0];
86   }
87
88   return s[count % n];
89 }
90
91 static double start;         /* start time of command */
92 static int count;            /* number of progress notifications per cmd */
93 static struct rmsd rmsd;     /* running mean and standard deviation */
94
95 /* This function is called just before we issue any command. */
96 void
97 reset_progress_bar (void)
98 {
99   /* The time at which this command was issued. */
100   struct timeval start_t;
101   gettimeofday (&start_t, NULL);
102
103   start = start_t.tv_sec + start_t.tv_usec / 1000000.;
104
105   count = 0;
106
107   rmsd_init (&rmsd);
108 }
109
110 /* Return remaining time estimate (in seconds) for current call.
111  *
112  * This returns the running mean estimate of remaining time, but if
113  * the latest estimate of total time is greater than two s.d.'s from
114  * the running mean then we don't print anything because we're not
115  * confident that the estimate is meaningful.  (Returned value is <0.0
116  * when nothing should be printed).
117  */
118 static double
119 estimate_remaining_time (double ratio)
120 {
121   if (ratio <= 0.)
122     return -1.0;
123
124   struct timeval now_t;
125   gettimeofday (&now_t, NULL);
126
127   double now = now_t.tv_sec + now_t.tv_usec / 1000000.;
128   /* We've done 'ratio' of the work in 'now - start' seconds. */
129   double time_passed = now - start;
130
131   double total_time = time_passed / ratio;
132
133   /* Add total_time to running mean and s.d. and then see if our
134    * estimate of total time is meaningful.
135    */
136   rmsd_add_sample (&rmsd, total_time);
137
138   double mean = rmsd_get_mean (&rmsd);
139   double sd = rmsd_get_standard_deviation (&rmsd);
140   if (fabs (total_time - mean) >= 2.0*sd)
141     return -1.0;
142
143   /* Don't return early estimates. */
144   if (time_passed < 3.0)
145     return -1.0;
146
147   return total_time - time_passed;
148 }
149
150 /* The overhead is how much we subtract before we get to the progress
151  * bar itself.
152  *
153  * / 100% [########---------------] xx:xx
154  * | |    |                       | |
155  * | |    |                       | time (5 cols)
156  * | |    |                       |
157  * | |    open paren + close paren + space (3 cols)
158  * | |
159  * | percentage and space (5 cols)
160  * |
161  * spinner and space (2 cols)
162  *
163  * Total = 2 + 5 + 3 + 5 = 15
164  */
165 #define COLS_OVERHEAD 15
166
167 /* Callback which displays a progress bar. */
168 void
169 progress_callback (guestfs_h *g, void *data,
170                    uint64_t event, int event_handle, int flags,
171                    const char *buf, size_t buf_len,
172                    const uint64_t *array, size_t array_len)
173 {
174   int i, cols, pulse_mode;
175   double ratio;
176   const char *s_open, *s_dot, *s_dash, *s_close;
177
178   if (utf8_mode) {
179     s_open = "\u27e6"; s_dot = "\u2589"; s_dash = "\u2550"; s_close = "\u27e7";
180   } else {
181     s_open = "["; s_dot = "#"; s_dash = "-"; s_close = "]";
182   }
183
184   if (array_len < 4)
185     return;
186
187   /*uint64_t proc_nr = array[0];*/
188   /*uint64_t serial = array[1];*/
189   uint64_t position = array[2];
190   uint64_t total = array[3];
191
192   if (have_terminfo == 0) {
193   dumb:
194     printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
195   } else {
196     cols = tgetnum ((char *) "co");
197     if (cols < 32) goto dumb;
198
199     /* Update an existing progress bar just printed? */
200     if (count > 0)
201       tputs (UP, 2, putchar);
202     count++;
203
204     /* Find out if we're in "pulse mode". */
205     pulse_mode = position == 0 && total == 1;
206
207     ratio = (double) position / total;
208     if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1;
209
210     if (pulse_mode) {
211       printf ("%s --- ", spinner (count));
212     }
213     else if (ratio < 1) {
214       int percent = 100.0 * ratio;
215       printf ("%s%3d%% ", spinner (count), percent);
216     }
217     else {
218       fputs (" 100% ", stdout);
219     }
220
221     fputs (s_open, stdout);
222
223     if (!pulse_mode) {
224       int dots = ratio * (double) (cols - COLS_OVERHEAD);
225
226       for (i = 0; i < dots; ++i)
227         fputs (s_dot, stdout);
228       for (i = dots; i < cols - COLS_OVERHEAD; ++i)
229         fputs (s_dash, stdout);
230     }
231     else {           /* "Pulse mode": the progress bar just pulses. */
232       for (i = 0; i < cols - COLS_OVERHEAD; ++i) {
233         int cc = (count * 3 - i) % (cols - COLS_OVERHEAD);
234         if (cc >= 0 && cc <= 3)
235           fputs (s_dot, stdout);
236         else
237           fputs (s_dash, stdout);
238       }
239     }
240
241     fputs (s_close, stdout);
242     fputc (' ', stdout);
243
244     /* Time estimate. */
245     double estimate = estimate_remaining_time (ratio);
246     if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) {
247       /* Display hours<h> */
248       estimate /= 60. * 60.;
249       int hh = floor (estimate);
250       printf (">%dh", hh);
251     } else if (estimate >= 100.0 * 60.0 /* >= 100 minutes */) {
252       /* Display hours<h>minutes */
253       estimate /= 60. * 60.;
254       int hh = floor (estimate);
255       double ignore;
256       int mm = floor (modf (estimate, &ignore) * 60.);
257       printf ("%02dh%02d", hh, mm);
258     } else if (estimate >= 0.0) {
259       /* Display minutes:seconds */
260       estimate /= 60.;
261       int mm = floor (estimate);
262       double ignore;
263       int ss = floor (modf (estimate, &ignore) * 60.);
264       printf ("%02d:%02d", mm, ss);
265     }
266     else /* < 0 means estimate was not meaningful */
267       fputs ("--:--", stdout);
268
269     fputc ('\n', stdout);
270   }
271 }