Skip to content

Commit 1eb1da2

Browse files
committed
ui: add refreshing progressbar with smooth ETA
1 parent 2c63f8d commit 1eb1da2

File tree

12 files changed

+353
-286
lines changed

12 files changed

+353
-286
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ COVERAGEFLAGS = -O0 -D NDEBUG -std=gnu99 -g2 --coverage -fprofile-update=atomic
1717

1818
SOURCES = main.c thpool.c file.c chunk.c line.c dedupe.c \
1919
optparse.c config.c error.c memstate.c meminfo.c bytesize.c \
20-
hmap.c status.c uinput.c \
20+
hmap.c status.c progressbar.c cprintferr.c \
2121

2222
COMMON = include/const.h include/debug.h
2323
OBJECTS = $(patsubst %.c, objects/%.o, $(SOURCES))

include/const.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,5 @@
2626
/* minimum needed memory (change with care... can throw bugs if too low */
2727
#define MIN_MEMORY (65536)
2828

29-
/* display program status periodically instead of waiting keypress */
30-
#define DEBUG_PROGRAM_STATUS (0)
31-
3229
/* max supported max-line-size */
3330
#define MAX_MAX_LINE_SIZE 4095

include/cprintferr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#pragma once
2+
3+
/* source file: cprintferr.c */
4+
int cprintferr(const char *fmt, ...);

include/debug.h

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,33 @@
99
# include <stdio.h>
1010
# include <stdarg.h>
1111
# include <time.h>
12+
# include "cprintferr.h"
1213

1314
# pragma GCC diagnostic push
1415
# pragma GCC diagnostic ignored "-Wunused-function"
1516
static void _dlog(int level, const char *file, int line, const char *fmt, ...)
1617
{
17-
char out[1024] = {0};
18-
int i = 0;
19-
2018
time_t now;
2119
struct tm* tm_info;
22-
23-
va_list ap;
24-
25-
if (isatty(STDERR_FILENO))
26-
i = snprintf(&out[i], 64, "\r\033[2K\e[34;1m[\e[33;1mDLOG%d\e[0;35m ", level);
27-
else
28-
i = snprintf(&out[i], 64, "[DLOG%d ", level);
29-
20+
char time_str[12];
3021
time(&now);
3122
tm_info = localtime(&now);
32-
i += strftime(&out[i], 12, "%d %H:%M:%S", tm_info);
33-
34-
if (isatty(STDERR_FILENO))
35-
i += snprintf(&out[i], 128, " \e[1;35m%10s:%-3d\e[34;1m]:\e[0m ",
36-
&file[4], line);
37-
else
38-
i += snprintf(&out[i], 128, " %10s:%-3d]: ",
39-
&file[4], line);
23+
strftime(time_str, 12, "%d %H:%M:%S", tm_info);
4024

25+
va_list ap;
26+
char msg[256];
4127
va_start(ap, fmt);
42-
i += vsnprintf(&out[i], sizeof(out) - i, fmt, ap);
28+
vsnprintf(msg, sizeof(msg), fmt, ap);
4329
va_end(ap);
4430

45-
out[i++] = '\n';
46-
47-
write(STDERR_FILENO, out, i);
31+
cprintferr(
32+
"\r\e[2K"
33+
"\e[34;1m["
34+
"\e[33;1mDLOG%d \e[0;35m%s \e[1;35m%13s:%-3d"
35+
"\e[34;1m]"
36+
":\e[0m %s\n",
37+
level, time_str, file+4, line, msg
38+
);
4839
}
4940
# pragma GCC diagnostic pop
5041

include/progressbar.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
/* source file: progressbar.c */
4+
void start_progressbar(void);
5+
void stop_progressbar(void);

include/status.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ enum e_status_set
2424

2525
/* source file: status.c */
2626
void display_report(void);
27-
void display_status(void);
27+
void display_status(int finished);
2828
void update_status(enum e_status_update action);
2929
void set_status(enum e_status_set var, size_t val);

include/uinput.h

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/cprintferr.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <stdio.h>
2+
#include <stdarg.h>
3+
#include <unistd.h>
4+
#include <string.h>
5+
6+
static int strip_ansi_inplace(char *s)
7+
{
8+
char *src = s, *dst = s;
9+
10+
while (*src) {
11+
if (*src == '\x1b' && src[1] == '[') {
12+
src += 2;
13+
while (*src && !(*src >= '@' && *src <= '~'))
14+
++src;
15+
if (*src) ++src;
16+
} else {
17+
*dst++ = *src++;
18+
}
19+
}
20+
*dst = '\0';
21+
return (int)(dst - s);
22+
}
23+
24+
/* color‑aware stderr printf */
25+
int cprintferr(const char *fmt, ...)
26+
{
27+
static int color_ok = -1;
28+
if (color_ok == -1)
29+
color_ok = isatty(STDERR_FILENO);
30+
31+
char buf[1024];
32+
va_list ap;
33+
va_start(ap, fmt);
34+
int len = vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
35+
va_end(ap);
36+
37+
if (len < 1)
38+
return len;
39+
else if (len >= (int)sizeof(buf) -1)
40+
len = sizeof(buf) - 2;
41+
42+
if (!color_ok) {
43+
len = strip_ansi_inplace(buf);
44+
if (len > 0 && buf[len-1] != '\n') {
45+
buf[len++] = '\n';
46+
buf[len] = '\0';
47+
}
48+
}
49+
if (len > 0) {
50+
fwrite(buf, 1, len, stderr);
51+
if (buf[len-1] != '\n')
52+
fflush(stderr);
53+
}
54+
return len;
55+
}

src/main.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "file.h"
88
#include "hmap.h"
99
#include "dedupe.h"
10-
#include "uinput.h"
10+
#include "progressbar.h"
1111
#include "status.h"
1212
#include "error.h"
1313

@@ -72,8 +72,7 @@ int main(int argc, char **argv)
7272
{
7373
optparse(argc, argv); /* setup g_conf options */
7474

75-
if (isatty(STDIN_FILENO))
76-
watch_user_input();
75+
start_progressbar();
7776

7877
update_status(FCOPY_START);
7978
init_files();
@@ -89,6 +88,8 @@ int main(int argc, char **argv)
8988
remove_duplicates();
9089
cleanup_files();
9190

91+
stop_progressbar();
92+
9293
display_report();
9394
return (0);
9495
}

src/progressbar.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include <stdlib.h>
2+
#include <unistd.h>
3+
#include <pthread.h>
4+
#include <string.h>
5+
#include <termios.h>
6+
#include <signal.h>
7+
#include <stdio.h>
8+
#include "progressbar.h"
9+
#include "status.h"
10+
#include "const.h"
11+
#include "error.h"
12+
13+
#define BUF_SIZE (1024)
14+
15+
static struct termios g_old_tio, g_new_tio;
16+
17+
static pthread_t g_pbarthread_id = 0;
18+
static volatile sig_atomic_t g_pbarthread_running = 1;
19+
20+
21+
/** thread worker
22+
* print progression status with print_status() on keypress
23+
*/
24+
#define SLEEPTIME_MS 250 // pbar refresh frequency
25+
#define ITER 25 // 25 loop iterations per loop refresh
26+
static void *progressbar_worker(void *arg)
27+
{
28+
(void)arg;
29+
int iter = ITER;
30+
while (g_pbarthread_running)
31+
{
32+
if (iter == ITER) {
33+
display_status(0);
34+
iter = 0;
35+
}
36+
usleep((SLEEPTIME_MS * 1000) / ITER); // 250ms / iter
37+
++iter;
38+
}
39+
display_status(1);
40+
return NULL;
41+
}
42+
43+
44+
/** restore terminal's line buffering & echo
45+
*/
46+
static void restore_termios(void)
47+
{
48+
tcsetattr(STDERR_FILENO, TCSANOW, &g_old_tio);
49+
}
50+
51+
/** disable terminal's line buffering & echo
52+
*/
53+
static void config_termios(void)
54+
{
55+
if (tcgetattr(STDERR_FILENO, &g_old_tio) < 0)
56+
error("tcgetattr(): %s", ERRNO);
57+
58+
g_new_tio = g_old_tio;
59+
g_new_tio.c_lflag &= ~(ICANON | ECHO);
60+
61+
if (tcsetattr(STDERR_FILENO, TCSANOW, &g_new_tio) < 0)
62+
error("tcsetattr(): %s", ERRNO);
63+
64+
atexit(restore_termios);
65+
}
66+
67+
68+
void start_progressbar(void)
69+
{
70+
if (!isatty(STDERR_FILENO))
71+
return ;
72+
73+
/* Ignore if not in foreground */
74+
if (tcgetpgrp(STDERR_FILENO) != getpgrp())
75+
return;
76+
77+
config_termios();
78+
if (pthread_create(
79+
&g_pbarthread_id, NULL,
80+
&progressbar_worker, NULL
81+
) < 0) {
82+
error("cannot create watch_user_input_worker() thread: %s", ERRNO);
83+
}
84+
}
85+
86+
void stop_progressbar(void)
87+
{
88+
if (g_pbarthread_id == 0)
89+
return;
90+
g_pbarthread_running = 0;
91+
pthread_join(g_pbarthread_id, NULL); // wait thread (ignored if already detached)
92+
restore_termios();
93+
g_pbarthread_id = 0;
94+
}

0 commit comments

Comments
 (0)