Commit | Line | Data |
---|---|---|
cd6eca43 MW |
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 | ||
b93f7054 MW |
34 | #include <unistd.h> |
35 | ||
cd6eca43 MW |
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; | |
b93f7054 | 134 | struct stat st; |
cd6eca43 MW |
135 | off_t o, sz = -1; |
136 | size_t n; | |
137 | ||
138 | /* --- Set up the offset --- */ | |
139 | ||
b93f7054 MW |
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; | |
cd6eca43 MW |
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", | |
bb77b1d1 MW |
181 | (int)(sizeof(f->name) + 2*PRHUMAN_DATAWD + |
182 | PRHUMAN_TIMEWD + BARWD + 16), | |
cd6eca43 MW |
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 -------------------------------------------------*/ |