chiark / gitweb /
Merge branch '2.4.x' into 2.5.x
[catacomb] / progs / cc-progress.c
1 /* -*-c-*-
2  *
3  * Progress indicators for command-line tools
4  *
5  * (c) 2011 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * Catacomb is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #define _FILE_OFFSET_BITS 64
31
32 #include "config.h"
33
34 #include <unistd.h>
35
36 #include "cc.h"
37
38 #ifndef PATHSEP
39 #  if defined(__riscos)
40 #    define PATHSEP '.'
41 #  elif defined(__unix) || defined(unix)
42 #    define PATHSEP '/'
43 #  else
44 #    define PATHSEP '\\'
45 #  endif
46 #endif
47
48 /*----- Static data -------------------------------------------------------*/
49
50 static const char baton[] = "-\\|/";
51
52 /*----- Human-friendly unit printing --------------------------------------*/
53
54 struct unit {
55   const char *name;
56   int m;
57 };
58
59 /* --- @prhuman_time@ --- *
60  *
61  * Arguments:   @FILE *fp@ = stream to print on
62  *              @unsigned long n@ = time in seconds to print
63  *
64  * Returns:     ---
65  *
66  * Use:         Prints a time in some reasonable format.  The time takes up
67  *              PRHUMAN_TIMEWD character spaces.
68  */
69
70 #define PRHUMAN_TIMEWD 7
71
72 static void prhuman_time(FILE *fp, unsigned long n)
73 {
74   const static struct unit utime[] = {
75     { "s",  60 }, { "m", 60 }, { "h", 24 }, { "d", 0 }
76   };
77
78   unsigned long m = 0;
79   const struct unit *u = utime;
80
81   while (u[1].m && n > u[0].m*u[1].m) { n /= u->m; u++; }
82   m = n / u[1].m; n %= u[0].m;
83   if (m) fprintf(fp, "%3lu%s%02lu%s", m, u[1].name, n, u[0].name);
84   else fprintf(fp, "    %2lu%s", n, u[0].name);
85 }
86
87 /* --- @prhuman_data@ --- *
88  *
89  * Arguments:   @FILE *fp@ = file to print on
90  *              @off_t n@ = size to be printed
91  *
92  * Returns:     ---
93  *
94  * Use:         Prints a data size in some reasonable format.  The data size
95  *              takes up PRHUMAN_DATAWD character spaces.
96  */
97
98 #define PRHUMAN_DATAWD 7
99
100 static void prhuman_data(FILE *fp, off_t n)
101 {
102   const static struct unit udata[] = {
103     { " ", 1024 }, { "k", 1024 }, { "M", 1024 }, { "G", 1024 },
104     { "T", 1024 }, { "P", 1024 }, { "E", 1024 }, { "Z", 1024 },
105     { "Y",    0 }
106   };
107
108   double x = n;
109   const struct unit *u = udata;
110
111   while (u->m && x >= u->m) { x /= u->m; u++; }
112   fprintf(fp, "%6.1f%s", x, u->name);
113 }
114
115 /*----- Main code ---------------------------------------------------------*/
116
117 #define BARWD 16
118
119 /* --- @fprogress_init@ --- *
120  *
121  * Arguments:   @fprogress *f@ = progress context to be initialized
122  *              @const char *name@ = file name string to show
123  *              @FILE *fp@ = file we're reading from
124  *
125  * Returns:     Zero on success, nonzero if the file's state is now broken.
126  *
127  * Use:         Initializes a progress context.  Nothing is actually
128  *              displayed yet.
129  */
130
131 int fprogress_init(fprogress *f, const char *name, FILE *fp)
132 {
133   const char *p;
134   struct stat st;
135   off_t o, sz = -1;
136   size_t n;
137
138   /* --- Set up the offset --- */
139
140   o = lseek(fileno(fp), 0, SEEK_CUR);
141   if (fstat(fileno(fp), &st)) return (-1);
142   sz = (S_ISREG(st.st_mode)) ? st.st_size : -1;
143   if (o != -1 && sz != -1) sz -= o;
144   f->o = f->olast = 0; f->sz = sz;
145
146   /* --- Set up the file name --- */
147
148   n = strlen(name);
149   if (n < sizeof(f->name))
150     strcpy(f->name, name);
151   else if ((p = strchr(name + n - sizeof(f->name) + 4, PATHSEP)) != 0)
152     sprintf(f->name, "...%s", p);
153   else {
154     p = strrchr(name, PATHSEP);
155     if (!p) sprintf(f->name, "%.*s...", (int)sizeof(f->name) - 4, name);
156     else sprintf(f->name, "...%.*s...", (int)sizeof(f->name) - 7, p);
157   }
158
159   /* --- Set up some other stuff --- */
160
161   f->start = f->last = time(0);
162   f->bp = baton;
163
164   /* --- Done --- */
165
166   return (0);
167 }
168
169 /* --- @fprogress_clear@ --- *
170  *
171  * Arguments:   @fprogress *f@ = progress context
172  *
173  * Returns:     ---
174  *
175  * Use:         Clears the progress display from the screen.
176  */
177
178 void fprogress_clear(fprogress *f)
179 {
180   fprintf(stderr, "\r%*s\r",
181           (int)(sizeof(f->name) + 2*PRHUMAN_DATAWD +
182                 PRHUMAN_TIMEWD + BARWD + 16),
183           "");
184 }
185
186 /* --- @fprogress_update@ --- *
187  *
188  * Arguments:   @fprogress *f@ = progress context
189  *              @size_t n@ = how much progress has been made
190  *
191  * Returns:     ---
192  *
193  * Use:         Maybe updates the display to show that some progress has been
194  *              made.
195  */
196
197 void fprogress_update(fprogress *f, size_t sz)
198 {
199   time_t now = time(0);
200   int i, n;
201
202   /* --- See if there's anything to do --- */
203
204   f->o += sz;
205   if (difftime(now, f->last) < 1) return;
206   f->last = now;
207
208   /* --- See if we're going to lose the ETA and percentage indicators --- */
209
210   if (f->olast < f->sz && f->o > f->sz) fprogress_clear(f);
211   f->olast = f->o;
212
213   /* --- Do the initial display --- */
214
215   fprintf(stderr, "\r%-*s%c ",
216           (int)sizeof(f->name), f->name,
217           *f->bp++);
218   if (!*f->bp) f->bp = baton;
219   prhuman_data(stderr, f->o);
220
221   /* --- More complicated display if we have a set size --- */
222
223   if (f->sz > f->o) {
224     fputc('/', stderr);
225     prhuman_data(stderr, f->sz);
226     fputs(" [", stderr);
227     n = (f->o*BARWD + f->sz/2)/f->sz;
228     for (i = 0; i < n; i++) fputc('.', stderr);
229     fprintf(stderr, "%*s] %3d%% ETA ", BARWD - n, "",
230             (int)((f->o*100 + 50)/f->sz));
231     prhuman_time(stderr, difftime(now, f->start)*(f->sz - f->o)/f->o);
232   }
233 }
234
235 /* --- @fprogress_done@ --- *
236  *
237  * Arguments:   @fprogress *f@ = progress context
238  *
239  * Returns:     ---
240  *
241  * Use:         Clear up the progress context and removes any display.
242  */
243
244 void fprogress_done(fprogress *f) { fprogress_clear(f); }
245
246 /*----- That's all, folks -------------------------------------------------*/