Line data Source code
1 : /*
2 : * This file is part of rmlint.
3 : *
4 : * rmlint 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 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * rmlint 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 rmlint. If not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Authors:
18 : *
19 : * - Christopher <sahib> Pahl 2010-2015 (https://github.com/sahib)
20 : * - Daniel <SeeSpotRun> T. 2014-2015 (https://github.com/SeeSpotRun)
21 : *
22 : * Hosted on http://github.com/sahib/rmlint
23 : *
24 : */
25 :
26 : #include "../formats.h"
27 :
28 : #include <glib.h>
29 : #include <stdio.h>
30 : #include <string.h>
31 : #include <math.h>
32 :
33 : #include <sys/ioctl.h>
34 :
35 : typedef struct RmFmtHandlerProgress {
36 : /* must be first */
37 : RmFmtHandler parent;
38 :
39 : /* user data */
40 : gdouble percent;
41 : gdouble last_unknown_pos;
42 : RmOff total_lint_bytes;
43 :
44 : char text_buf[1024];
45 : guint32 text_len;
46 : guint32 update_counter;
47 : guint32 update_interval;
48 : guint8 use_unicode_glyphs;
49 :
50 : bool plain;
51 :
52 : RmFmtProgressState last_state;
53 : struct winsize terminal;
54 : } RmFmtHandlerProgress;
55 :
56 122 : static void rm_fmt_progress_format_preprocess(RmSession *session, char *buf,
57 : size_t buf_len, FILE *out) {
58 122 : if(session->offsets_read > 0) {
59 0 : g_snprintf(buf, buf_len, "fiemap: %s+%" LLU "%s %s-%" LLU "%s %s#%" LLU "%s",
60 0 : MAYBE_GREEN(out, session), session->offsets_read,
61 0 : MAYBE_RESET(out, session), MAYBE_RED(out, session),
62 0 : session->offset_fails, MAYBE_RESET(out, session),
63 0 : MAYBE_BLUE(out, session), session->total_filtered_files,
64 0 : MAYBE_RESET(out, session));
65 : } else {
66 244 : g_snprintf(buf, buf_len, "%s %s%" LLU "%s", _("reduces files to"),
67 122 : MAYBE_GREEN(out, session), session->total_filtered_files,
68 122 : MAYBE_RESET(out, session));
69 : }
70 122 : }
71 :
72 732 : static void rm_fmt_progress_format_text(RmSession *session, RmFmtHandlerProgress *self,
73 : int max_len, FILE *out) {
74 732 : char num_buf[32] = {0};
75 732 : char preproc_buf[128] = {0};
76 732 : memset(num_buf, 0, sizeof(num_buf));
77 732 : memset(preproc_buf, 0, sizeof(preproc_buf));
78 :
79 732 : switch(self->last_state) {
80 : case RM_PROGRESS_STATE_TRAVERSE:
81 244 : self->percent = 2.0;
82 1952 : self->text_len = g_snprintf(
83 244 : self->text_buf, sizeof(self->text_buf), "%s (%s%d%s %s / %s%d%s + %s%d%s %s)",
84 244 : _("Traversing"), MAYBE_GREEN(out, session), session->total_files,
85 488 : MAYBE_RESET(out, session), _("usable files"), MAYBE_RED(out, session),
86 488 : session->ignored_files, MAYBE_RESET(out, session), MAYBE_RED(out, session),
87 244 : session->ignored_folders, MAYBE_RESET(out, session),
88 : _("ignored files / folders"));
89 244 : break;
90 : case RM_PROGRESS_STATE_PREPROCESS:
91 122 : self->percent = 2.0;
92 122 : rm_fmt_progress_format_preprocess(session, preproc_buf, sizeof(preproc_buf), out);
93 488 : self->text_len = g_snprintf(
94 122 : self->text_buf, sizeof(self->text_buf), "%s (%s / %s %s%" LLU "%s %s)",
95 122 : _("Preprocessing"), preproc_buf, _("found"), MAYBE_RED(out, session),
96 122 : session->other_lint_cnt, MAYBE_RESET(out, session), _("other lint"));
97 122 : break;
98 : case RM_PROGRESS_STATE_SHREDDER:
99 488 : self->percent = 1.0 - ((gdouble)session->shred_bytes_remaining /
100 244 : (gdouble)session->shred_bytes_after_preprocess);
101 244 : rm_util_size_to_human_readable(session->shred_bytes_remaining, num_buf,
102 : sizeof(num_buf));
103 2440 : self->text_len = g_snprintf(
104 244 : self->text_buf, sizeof(self->text_buf),
105 : "%s (%s%" LLU "%s %s %s%" LLU "%s %s; %s%s%s %s %s%" LLU "%s %s)",
106 244 : _("Matching"), MAYBE_RED(out, session), session->dup_counter,
107 488 : MAYBE_RESET(out, session), _("dupes of"), MAYBE_YELLOW(out, session),
108 244 : session->dup_group_counter, MAYBE_RESET(out, session), _("originals"),
109 488 : MAYBE_GREEN(out, session), num_buf, MAYBE_RESET(out, session),
110 244 : _("to scan in"), MAYBE_GREEN(out, session), session->shred_files_remaining,
111 244 : MAYBE_RESET(out, session), _("files"));
112 244 : break;
113 : case RM_PROGRESS_STATE_MERGE:
114 0 : self->percent = 1.0;
115 0 : self->text_len = g_snprintf(self->text_buf, sizeof(self->text_buf),
116 0 : _("Merging files into directories (stand by...)"));
117 0 : break;
118 : case RM_PROGRESS_STATE_INIT:
119 : case RM_PROGRESS_STATE_PRE_SHUTDOWN:
120 : case RM_PROGRESS_STATE_SUMMARY:
121 : default:
122 122 : self->percent = 0;
123 122 : memset(self->text_buf, 0, sizeof(self->text_buf));
124 122 : break;
125 : }
126 :
127 : /* Support unicode messages - tranlsated text might contain some. */
128 732 : self->text_len = g_utf8_strlen(self->text_buf, self->text_len);
129 :
130 : /* Get rid of colors */
131 732 : int text_iter = 0;
132 732 : for(char *iter = &self->text_buf[0]; *iter; iter++) {
133 610 : if(*iter == '\x1b') {
134 0 : char *jump = strchr(iter, 'm');
135 0 : if(jump != NULL) {
136 0 : self->text_len -= jump - iter + 1;
137 0 : iter = jump;
138 0 : continue;
139 : }
140 : }
141 :
142 610 : if(text_iter >= max_len) {
143 610 : *iter = 0;
144 610 : self->text_len = text_iter;
145 610 : break;
146 : }
147 :
148 0 : text_iter++;
149 : }
150 732 : }
151 :
152 732 : static void rm_fmt_progress_print_text(RmFmtHandlerProgress *self, int width, FILE *out) {
153 732 : if(self->text_len < (unsigned)width) {
154 0 : for(guint32 i = 0; i < width - self->text_len; ++i) {
155 0 : fprintf(out, " ");
156 : }
157 : }
158 :
159 732 : fprintf(out, "%s", self->text_buf);
160 732 : }
161 :
162 : typedef enum RmProgressBarGlyph {
163 : PROGRESS_ARROW,
164 : PROGRESS_TICK_LOW,
165 : PROGRESS_TICK_HIGH,
166 : PROGRESS_TICK_SPACE,
167 : PROGRESS_EMPTY,
168 : PROGRESS_FULL,
169 : PROGRESS_LEFT_BRACKET,
170 : PROGRESS_RIGHT_BRACKET
171 : } RmProgressBarGlyph;
172 :
173 : static const char *PROGRESS_FANCY_UNICODE_TABLE[] = {[PROGRESS_ARROW] = "➤",
174 : [PROGRESS_TICK_LOW] = "□",
175 : [PROGRESS_TICK_HIGH] = "▢",
176 : [PROGRESS_TICK_SPACE] = " ",
177 : [PROGRESS_EMPTY] = "⌿",
178 : [PROGRESS_FULL] = "—",
179 : [PROGRESS_LEFT_BRACKET] = "⦃",
180 : [PROGRESS_RIGHT_BRACKET] = "⦄"};
181 :
182 : static const char *PROGRESS_FANCY_ASCII_TABLE[] = {[PROGRESS_ARROW] = ">",
183 : [PROGRESS_TICK_LOW] = "o",
184 : [PROGRESS_TICK_HIGH] = "O",
185 : [PROGRESS_TICK_SPACE] = " ",
186 : [PROGRESS_EMPTY] = "/",
187 : [PROGRESS_FULL] = "_",
188 : [PROGRESS_LEFT_BRACKET] = "{",
189 : [PROGRESS_RIGHT_BRACKET] = "}"};
190 :
191 : static const char *PROGRESS_PLAIN_UNICODE_TABLE[] = {[PROGRESS_ARROW] = "▒",
192 : [PROGRESS_TICK_LOW] = "░",
193 : [PROGRESS_TICK_HIGH] = "▒",
194 : [PROGRESS_TICK_SPACE] = "░",
195 : [PROGRESS_EMPTY] = "░",
196 : [PROGRESS_FULL] = "▓",
197 : [PROGRESS_LEFT_BRACKET] = "▕",
198 : [PROGRESS_RIGHT_BRACKET] = "▏"};
199 :
200 : static const char *PROGRESS_PLAIN_ASCII_TABLE[] = {[PROGRESS_ARROW] = "_",
201 : [PROGRESS_TICK_LOW] = " ",
202 : [PROGRESS_TICK_HIGH] = "_",
203 : [PROGRESS_TICK_SPACE] = " ",
204 : [PROGRESS_EMPTY] = "\\",
205 : [PROGRESS_FULL] = "/",
206 : [PROGRESS_LEFT_BRACKET] = "|",
207 : [PROGRESS_RIGHT_BRACKET] = "|"};
208 :
209 1952 : static const char *rm_fmt_progressbar_get_glyph(RmFmtHandlerProgress *self,
210 : RmProgressBarGlyph type) {
211 1952 : if(self->plain && self->use_unicode_glyphs) {
212 1888 : return PROGRESS_PLAIN_UNICODE_TABLE[type];
213 64 : } else if(self->plain) {
214 32 : return PROGRESS_PLAIN_ASCII_TABLE[type];
215 32 : } else if(self->use_unicode_glyphs) {
216 32 : return PROGRESS_FANCY_UNICODE_TABLE[type];
217 : } else {
218 0 : return PROGRESS_FANCY_ASCII_TABLE[type];
219 : }
220 : }
221 :
222 1952 : static void rm_fmt_progressbar_print_glyph(FILE *out, RmSession *session,
223 : RmFmtHandlerProgress *self,
224 : RmProgressBarGlyph type, const char *color) {
225 1952 : fprintf(out, "%s%s%s", MAYBE_COLOR(out, session, color),
226 1952 : rm_fmt_progressbar_get_glyph(self, type), MAYBE_COLOR(out, session, RESET));
227 1952 : }
228 :
229 976 : static void rm_fmt_progress_print_bar(RmSession *session, RmFmtHandlerProgress *self,
230 : int width, FILE *out) {
231 976 : int cells = width * self->percent;
232 :
233 : /* true when we do not know when 100% is reached.
234 : * Show a moving something in this case.
235 : * */
236 976 : bool is_unknown = self->percent > 1.1;
237 :
238 976 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_LEFT_BRACKET, RED);
239 :
240 976 : for(int i = 0; i < width - 2; ++i) {
241 0 : if(i < cells) {
242 0 : if(is_unknown) {
243 0 : if((int)self->last_unknown_pos % 4 == i % 4) {
244 0 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_TICK_LOW,
245 : BLUE);
246 0 : } else if((int)self->last_unknown_pos % 2 == i % 2) {
247 0 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_TICK_HIGH,
248 : YELLOW);
249 : } else {
250 0 : rm_fmt_progressbar_print_glyph(out, session, self,
251 : PROGRESS_TICK_SPACE, GREEN);
252 : }
253 : } else {
254 0 : const char *color = (self->percent > 1.01) ? BLUE : GREEN;
255 0 : RmProgressBarGlyph glyph =
256 0 : (self->percent > 1.01) ? PROGRESS_EMPTY : PROGRESS_FULL;
257 0 : rm_fmt_progressbar_print_glyph(out, session, self, glyph, color);
258 : }
259 0 : } else if(i == cells) {
260 0 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_ARROW, YELLOW);
261 : } else {
262 0 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_EMPTY, BLUE);
263 : }
264 : }
265 :
266 976 : rm_fmt_progressbar_print_glyph(out, session, self, PROGRESS_RIGHT_BRACKET, RED);
267 :
268 976 : self->last_unknown_pos = fmod(self->last_unknown_pos + 0.005, width - 2);
269 976 : }
270 :
271 1830 : static void rm_fmt_prog(RmSession *session,
272 : RmFmtHandler *parent,
273 : FILE *out,
274 : RmFmtProgressState state) {
275 1830 : RmFmtHandlerProgress *self = (RmFmtHandlerProgress *)parent;
276 1830 : if(state == RM_PROGRESS_STATE_SUMMARY) {
277 122 : return;
278 : }
279 :
280 1708 : if(session->replay_files.length > 0) {
281 : /* Makes not much sense to print a progressbar with --replay */
282 0 : return;
283 : }
284 :
285 1708 : if(state == RM_PROGRESS_STATE_INIT) {
286 : /* Do initializiation here */
287 122 : const char *update_interval_str =
288 122 : rm_fmt_get_config_value(session->formats, "progressbar", "update_interval");
289 :
290 122 : self->plain = true;
291 122 : if(rm_fmt_get_config_value(session->formats, "progressbar", "fancy") != NULL) {
292 2 : self->plain = false;
293 : }
294 :
295 122 : self->use_unicode_glyphs = true;
296 122 : if(rm_fmt_get_config_value(session->formats, "progressbar", "ascii") != NULL) {
297 2 : self->use_unicode_glyphs = false;
298 : }
299 :
300 122 : if(update_interval_str) {
301 0 : self->update_interval = g_ascii_strtoull(update_interval_str, NULL, 10);
302 : }
303 :
304 122 : if(self->update_interval == 0) {
305 122 : self->update_interval = 50;
306 : }
307 :
308 122 : self->last_unknown_pos = 0;
309 122 : self->total_lint_bytes = 1;
310 :
311 122 : fprintf(out, "\e[?25l"); /* Hide the cursor */
312 122 : fflush(out);
313 122 : return;
314 : }
315 :
316 1586 : if(state == RM_PROGRESS_STATE_PRE_SHUTDOWN || rm_session_was_aborted(session)) {
317 122 : fprintf(out, "\e[?25h"); /* show the cursor */
318 122 : fflush(out);
319 :
320 122 : if(rm_session_was_aborted(session)) {
321 0 : return;
322 : }
323 : }
324 :
325 1586 : if(state == RM_PROGRESS_STATE_SHREDDER) {
326 244 : self->total_lint_bytes =
327 244 : MAX(self->total_lint_bytes, session->shred_bytes_remaining);
328 : }
329 :
330 1586 : if(self->last_state != state && self->last_state != RM_PROGRESS_STATE_INIT) {
331 366 : self->percent = 1.05;
332 366 : if(state != RM_PROGRESS_STATE_PRE_SHUTDOWN) {
333 244 : rm_fmt_progress_print_bar(session, self, self->terminal.ws_col * 0.3, out);
334 244 : fprintf(out, "\n");
335 : }
336 366 : self->update_counter = 0;
337 : }
338 :
339 1586 : if(state == RM_PROGRESS_STATE_TRAVERSE && session->traverse_finished) {
340 122 : self->update_counter = 0;
341 : }
342 :
343 1586 : if(state == RM_PROGRESS_STATE_SHREDDER && session->shredder_finished) {
344 122 : self->update_counter = 0;
345 : }
346 :
347 1586 : if(ioctl(fileno(out), TIOCGWINSZ, &self->terminal) != 0) {
348 : // rm_log_warning_line(_("Cannot figure out terminal width."));
349 : }
350 :
351 1586 : self->last_state = state;
352 :
353 1586 : if(self->update_counter++ % self->update_interval == 0) {
354 732 : int text_width = MAX(self->terminal.ws_col * 0.7 - 1, 0);
355 732 : rm_fmt_progress_format_text(session, self, text_width, out);
356 732 : if(state == RM_PROGRESS_STATE_PRE_SHUTDOWN) {
357 : /* do not overwrite last messages */
358 122 : self->percent = 1.05;
359 122 : text_width = 0;
360 : }
361 :
362 732 : rm_fmt_progress_print_bar(session, self, self->terminal.ws_col * 0.3, out);
363 732 : rm_fmt_progress_print_text(self, text_width, out);
364 732 : fprintf(out, "%s\r", MAYBE_RESET(out, session));
365 : }
366 :
367 1586 : if(state == RM_PROGRESS_STATE_PRE_SHUTDOWN) {
368 122 : fprintf(out, "\n\n");
369 : }
370 : }
371 :
372 : static RmFmtHandlerProgress PROGRESS_HANDLER_IMPL = {
373 : /* Initialize parent */
374 : .parent =
375 : {
376 : .size = sizeof(PROGRESS_HANDLER_IMPL),
377 : .name = "progressbar",
378 : .head = NULL,
379 : .elem = NULL,
380 : .prog = rm_fmt_prog,
381 : .foot = NULL,
382 : .valid_keys = {"update_interval", "ascii", "fancy", NULL},
383 : },
384 :
385 : /* Initialize own stuff */
386 : .percent = 0.0f,
387 : .text_len = 0,
388 : .text_buf = {0},
389 : .update_counter = 0,
390 : .use_unicode_glyphs = true,
391 : .plain = true,
392 : .last_state = RM_PROGRESS_STATE_INIT};
393 :
394 : RmFmtHandler *PROGRESS_HANDLER = (RmFmtHandler *)&PROGRESS_HANDLER_IMPL;
|