chiark / gitweb /
progs/cc-progress.c: Use `fstat' to discover the file size.
[catacomb] / progs / cc-progress.c
CommitLineData
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
50static const char baton[] = "-\\|/";
51
52/*----- Human-friendly unit printing --------------------------------------*/
53
54struct 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
72static 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
100static 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
131int 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
178void 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
197void 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
244void fprogress_done(fprogress *f) { fprogress_clear(f); }
245
246/*----- That's all, folks -------------------------------------------------*/