- markus@cvs.openbsd.org 2003/07/29 18:24:00
     [LICENCE progressmeter.c]
     replace 4 clause BSD licensed progressmeter code with a replacement
     from Nils Nordman and myself; ok deraadt@
     (copied from OpenBSD an re-applied portable changes)
diff --git a/progressmeter.c b/progressmeter.c
index 6aa225a..170d869 100644
--- a/progressmeter.c
+++ b/progressmeter.c
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 1999 Theo de Raadt.  All rights reserved.
- * Copyright (c) 1999 Aaron Campbell.  All rights reserved.
+ * Copyright (c) 2003 Nils Nordman.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -23,248 +22,242 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/*
- * Copyright (c) 1997-2003 The NetBSD Foundation, Inc.
- * All rights reserved.
- *
- * This code is derived from software contributed to The NetBSD Foundation
- * by Luke Mewburn.
- *
- * This code is derived from software contributed to The NetBSD Foundation
- * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
- * NASA Ames Research Center.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *	This product includes software developed by the NetBSD
- *	Foundation, Inc. and its contributors.
- * 4. Neither the name of The NetBSD Foundation nor the names of its
- *    contributors may be used to endorse or promote products derived
- *    from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
 #include "includes.h"
-RCSID("$OpenBSD: progressmeter.c,v 1.8 2003/06/28 16:23:06 deraadt Exp $");
+RCSID("$OpenBSD: progressmeter.c,v 1.13 2003/07/31 22:34:03 markus Exp $");
 
-#ifdef HAVE_LIBGEN_H
-#include <libgen.h>
-#endif
-
-#include "atomicio.h"
 #include "progressmeter.h"
+#include "atomicio.h"
 #include "misc.h"
 
-/* Number of seconds before xfer considered "stalled". */
-#define STALLTIME	5
-/* alarm() interval for updating progress meter. */
-#define PROGRESSTIME	1
+#define DEFAULT_WINSIZE 80
+#define MAX_WINSIZE 512
+#define PADDING 1		/* padding between the progress indicators */
+#define UPDATE_INTERVAL 1	/* update the progress meter every second */
+#define STALL_TIME 5		/* we're stalled after this many seconds */
 
-/* Signal handler used for updating the progress meter. */
+/* determines whether we can output to the terminal */
+static int can_output(void);
+
+/* formats and inserts the specified size into the given buffer */
+static void format_size(char *, int, off_t);
+static void format_rate(char *, int, off_t);
+
+/* updates the progressmeter to reflect the current state of the transfer */
+void refresh_progress_meter(void);
+
+/* signal handler for updating the progress meter */
 static void update_progress_meter(int);
 
-/* Returns non-zero if we are the foreground process. */
-static int foregroundproc(void);
+static time_t start; 		/* start progress */
+static time_t last_update; 	/* last progress update */
+static char *file; 		/* name of the file being transferred */
+static off_t end_pos; 		/* ending position of transfer */
+static off_t cur_pos; 		/* transfer position as of last refresh */
+static volatile off_t *counter;	/* progress counter */
+static long stalled; 		/* how long we have been stalled */
+static int bytes_per_second; 	/* current speed in bytes per second */
+static int win_size; 		/* terminal window size */
 
-/* Returns width of the terminal (for progress meter calculations). */
-static int get_tty_width(void);
+/* units for format_size */
+static const char unit[] = " KMGT";
 
-/* Visual statistics about files as they are transferred. */
-static void draw_progress_meter(void);
+static int
+can_output(void)
+{
+	return (getpgrp() == tcgetpgrp(STDOUT_FILENO));
+}
 
-/* Time a transfer started. */
-static struct timeval start;
+static void
+format_rate(char *buf, int size, off_t bytes)
+{
+	int i;
 
-/* Number of bytes of current file transferred so far. */
-static volatile off_t *statbytes;
+	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 / 100,
+	    (int64_t) (bytes + 5) / 10 % 10,
+	    unit[i],
+	    i ? "B" : " ");
+}
 
-/* Total size of current file. */
-static off_t totalbytes;
+static void
+format_size(char *buf, int size, off_t bytes)
+{
+	int i;
 
-/* Name of current file being transferred. */
-static char *curfile;
-
-/* Time of last update. */
-static struct timeval lastupdate;
-
-/* Size at the time of the last update. */
-static off_t lastsize;
+	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
-start_progress_meter(char *file, off_t filesize, off_t *counter)
+refresh_progress_meter(void)
 {
-	if ((curfile = basename(file)) == NULL)
-		curfile = file;
+	char buf[MAX_WINSIZE + 1];
+	time_t now;
+	off_t transferred;
+	double elapsed;
+	int percent;
+	int bytes_left;
+	int cur_speed;
+	int hours, minutes, seconds;
+	int i, len;
+	int file_len;
 
-	totalbytes = filesize;
-	statbytes = counter;
-	(void) gettimeofday(&start, (struct timezone *) 0);
-	lastupdate = start;
-	lastsize = 0;
+	transferred = *counter - cur_pos;
+	cur_pos = *counter;
+	now = time(NULL);
+	bytes_left = end_pos - cur_pos;
 
-	draw_progress_meter();
+	if (bytes_left > 0)
+		elapsed = now - last_update;
+	else
+		elapsed = now - start;
+
+	/* calculate speed */
+	if (elapsed != 0)
+		cur_speed = (transferred / elapsed);
+	else
+		cur_speed = 0;
+
+#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;
+
+	/* filename */
+	buf[0] = '\0';
+	file_len = win_size - 35;
+	if (file_len > 0) {
+		len = snprintf(buf, file_len, "\r%s", file);
+		for (i = len;  i < file_len; i++ )
+			buf[i] = ' ';
+		buf[file_len] = '\0';
+	}
+
+	/* percent of transfer done */
+	if (end_pos != 0)
+		percent = ((float)cur_pos / end_pos) * 100;
+	else
+		percent = 100;
+	snprintf(buf + strlen(buf), win_size - strlen(buf),
+	    " %3d%% ", percent);
+
+	/* amount transferred */
+	format_size(buf + strlen(buf), win_size - strlen(buf),
+	    cur_pos);
+	strlcat(buf, " ", win_size);
+
+	/* bandwidth usage */
+	format_rate(buf + strlen(buf), win_size - strlen(buf),
+	    bytes_per_second);
+	strlcat(buf, "/s ", win_size);
+
+	/* ETA */
+	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);
+	last_update = now;
+}
+
+static void
+update_progress_meter(int ignore)
+{
+	int save_errno;
+
+	save_errno = errno;
+
+	if (can_output())
+		refresh_progress_meter();
+
 	mysignal(SIGALRM, update_progress_meter);
-	alarm(PROGRESSTIME);
+	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;					/* trailing \0 */
+
+	if (can_output())
+		refresh_progress_meter();
+
+	mysignal(SIGALRM, update_progress_meter);
+	alarm(UPDATE_INTERVAL);
 }
 
 void
 stop_progress_meter(void)
 {
 	alarm(0);
-	draw_progress_meter();
-	if (foregroundproc() != 0)
-		atomicio(vwrite, fileno(stdout), "\n", 1);
-}
 
-static void
-update_progress_meter(int ignore)
-{
-	int save_errno = errno;
-
-	draw_progress_meter();
-	mysignal(SIGALRM, update_progress_meter);
-	alarm(PROGRESSTIME);
-	errno = save_errno;
-}
-
-static int
-foregroundproc(void)
-{
-	static pid_t pgrp = -1;
-	int ctty_pgrp;
-
-	if (pgrp == -1)
-		pgrp = getpgrp();
-
-#ifdef HAVE_TCGETPGRP
-        return ((ctty_pgrp = tcgetpgrp(STDOUT_FILENO)) != -1 &&
-	                ctty_pgrp == pgrp);
-#else
-	return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
-		 ctty_pgrp == pgrp));
-#endif
-}
-
-static void
-draw_progress_meter(void)
-{
-	static const char spaces[] = "                          "
-	    "                                                   "
-	    "                                                   "
-	    "                                                   "
-	    "                                                   "
-	    "                                                   ";
-	static const char prefixes[] = " KMGTP";
-	struct timeval now, td, wait;
-	off_t cursize, abbrevsize, bytespersec;
-	double elapsed;
-	int ratio, remaining, i, ai, bi, nspaces;
-	char buf[512];
-
-	if (foregroundproc() == 0)
+	if (!can_output())
 		return;
 
-	(void) gettimeofday(&now, (struct timezone *) 0);
-	cursize = *statbytes;
-	if (totalbytes != 0) {
-		ratio = 100.0 * cursize / totalbytes;
-		ratio = MAX(ratio, 0);
-		ratio = MIN(ratio, 100);
-	} else
-		ratio = 100;
+	/* Ensure we complete the progress */
+	if (cur_pos != end_pos)
+		refresh_progress_meter();
 
-	abbrevsize = cursize;
-	for (ai = 0; abbrevsize >= 10000 && ai < sizeof(prefixes); ai++)
-		abbrevsize >>= 10;
-
-	timersub(&now, &lastupdate, &wait);
-	if (cursize > lastsize) {
-		lastupdate = now;
-		lastsize = cursize;
-		wait.tv_sec = 0;
-	}
-	timersub(&now, &start, &td);
-	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
-
-	bytespersec = 0;
-	if (cursize > 0) {
-		bytespersec = cursize;
-		if (elapsed > 0.0)
-			bytespersec /= elapsed;
-	}
-	for (bi = 1; bytespersec >= 1024000 && bi < sizeof(prefixes); bi++)
-		bytespersec >>= 10;
-
-    	nspaces = MIN(get_tty_width() - 79, sizeof(spaces) - 1);
-
-	snprintf(buf, sizeof(buf),
-	    "\r%-45.45s%.*s%3d%% %4lld%c%c %3lld.%01d%cB/s",
-	    curfile,
-	    nspaces,
-	    spaces,
-	    ratio,
-	    (int64_t)abbrevsize,
-	    prefixes[ai],
-	    ai == 0 ? ' ' : 'B',
-	    (int64_t)(bytespersec / 1024),
-	    (int)((bytespersec % 1024) * 10 / 1024),
-	    prefixes[bi]
-	);
-
-	if (cursize <= 0 || elapsed <= 0.0 || cursize > totalbytes) {
-		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
-		    "   --:-- ETA");
-	} else if (wait.tv_sec >= STALLTIME) {
-		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
-		    " - stalled -");
-	} else {
-		if (cursize != totalbytes)
-			remaining = (int)(totalbytes / (cursize / elapsed) -
-			    elapsed);
-		else
-			remaining = elapsed;
-
-		i = remaining / 3600;
-		if (i)
-			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
-			    "%2d:", i);
-		else
-			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
-			    "   ");
-		i = remaining % 3600;
-		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
-		    "%02d:%02d%s", i / 60, i % 60,
-		    (cursize != totalbytes) ? " ETA" : "    ");
-	}
-	atomicio(vwrite, fileno(stdout), buf, strlen(buf));
-}
-
-static int
-get_tty_width(void)
-{
-	struct winsize winsize;
-
-	if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
-		return (winsize.ws_col ? winsize.ws_col : 80);
-	else
-		return (80);
+	atomicio(vwrite, STDOUT_FILENO, "\n", 1);
 }