#include "includes.h"
RCSID("$OpenBSD: progressmeter.c,v 1.19 2004/02/05 15:33:33 markus Exp $");
#include "progressmeter.h"
#include "atomicio.h"
#include "misc.h"
#define DEFAULT_WINSIZE 80
#define MAX_WINSIZE 512
#define PADDING 1
#define UPDATE_INTERVAL 1
#define STALL_TIME 5
static int can_output(void);
static void format_size(char *, int, off_t);
static void format_rate(char *, int, off_t);
void refresh_progress_meter(void);
static void update_progress_meter(int);
static time_t start;
static time_t last_update;
static char *file;
static off_t end_pos;
static off_t cur_pos;
static volatile off_t *counter;
static long stalled;
static int bytes_per_second;
static int win_size;
static const char unit[] = " KMGT";
static int
can_output(void)
{
return (getpgrp() == tcgetpgrp(STDOUT_FILENO));
}
static void
format_rate(char *buf, int size, off_t bytes)
{
int i;
bytes *= 100;
for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
bytes = (bytes + 512) / 1024;
if (i == 0) {
i++;
bytes = (bytes + 512) / 1024;
}
snprintf(buf, size, "%3lld.%1lld%c%s",
(int64_t) (bytes + 5) / 100,
(int64_t) (bytes + 5) / 10 % 10,
unit[i],
i ? "B" : " ");
}
static void
format_size(char *buf, int size, off_t bytes)
{
int i;
for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
bytes = (bytes + 512) / 1024;
snprintf(buf, size, "%4lld%c%s",
(int64_t) bytes,
unit[i],
i ? "B" : " ");
}
void
refresh_progress_meter(void)
{
char buf[MAX_WINSIZE + 1];
time_t now;
off_t transferred;
double elapsed;
int percent;
off_t bytes_left;
int cur_speed;
int hours, minutes, seconds;
int i, len;
int file_len;
transferred = *counter - cur_pos;
cur_pos = *counter;
now = time(NULL);
bytes_left = end_pos - cur_pos;
if (bytes_left > 0)
elapsed = now - last_update;
else {
elapsed = now - start;
transferred = end_pos;
bytes_per_second = 0;
}
if (elapsed != 0)
cur_speed = (transferred / elapsed);
else
cur_speed = transferred;
#define AGE_FACTOR 0.9
if (bytes_per_second != 0) {
bytes_per_second = (bytes_per_second * AGE_FACTOR) +
(cur_speed * (1.0 - AGE_FACTOR));
} else
bytes_per_second = cur_speed;
buf[0] = '\0';
file_len = win_size - 35;
if (file_len > 0) {
len = snprintf(buf, file_len + 1, "\r%s", file);
if (len < 0)
len = 0;
for (i = len; i < file_len; i++ )
buf[i] = ' ';
buf[file_len] = '\0';
}
if (end_pos != 0)
percent = ((float)cur_pos / end_pos) * 100;
else
percent = 100;
snprintf(buf + strlen(buf), win_size - strlen(buf),
" %3d%% ", percent);
format_size(buf + strlen(buf), win_size - strlen(buf),
cur_pos);
strlcat(buf, " ", win_size);
format_rate(buf + strlen(buf), win_size - strlen(buf),
bytes_per_second);
strlcat(buf, "/s ", win_size);
if (!transferred)
stalled += elapsed;
else
stalled = 0;
if (stalled >= STALL_TIME)
strlcat(buf, "- stalled -", win_size);
else if (bytes_per_second == 0 && bytes_left)
strlcat(buf, " --:-- ETA", win_size);
else {
if (bytes_left > 0)
seconds = bytes_left / bytes_per_second;
else
seconds = elapsed;
hours = seconds / 3600;
seconds -= hours * 3600;
minutes = seconds / 60;
seconds -= minutes * 60;
if (hours != 0)
snprintf(buf + strlen(buf), win_size - strlen(buf),
"%d:%02d:%02d", hours, minutes, seconds);
else
snprintf(buf + strlen(buf), win_size - strlen(buf),
" %02d:%02d", minutes, seconds);
if (bytes_left > 0)
strlcat(buf, " ETA", win_size);
else
strlcat(buf, " ", win_size);
}
atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1);
last_update = now;
}
static void
update_progress_meter(int ignore)
{
int save_errno;
save_errno = errno;
if (can_output())
refresh_progress_meter();
signal(SIGALRM, update_progress_meter);
alarm(UPDATE_INTERVAL);
errno = save_errno;
}
void
start_progress_meter(char *f, off_t filesize, off_t *stat)
{
struct winsize winsize;
start = last_update = time(NULL);
file = f;
end_pos = filesize;
cur_pos = 0;
counter = stat;
stalled = 0;
bytes_per_second = 0;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
winsize.ws_col != 0) {
if (winsize.ws_col > MAX_WINSIZE)
win_size = MAX_WINSIZE;
else
win_size = winsize.ws_col;
} else
win_size = DEFAULT_WINSIZE;
win_size += 1;
if (can_output())
refresh_progress_meter();
signal(SIGALRM, update_progress_meter);
alarm(UPDATE_INTERVAL);
}
void
stop_progress_meter(void)
{
alarm(0);
if (!can_output())
return;
if (cur_pos != end_pos)
refresh_progress_meter();
atomicio(vwrite, STDOUT_FILENO, "\n", 1);
}