progressmeter.c
changeset 0 1d0ce1ebbc72
equal deleted inserted replaced
-1:000000000000 0:1d0ce1ebbc72
       
     1 /*
       
     2  * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
       
     3  * Copyright (c) 2003 Nils Nordman.  All rights reserved.
       
     4  *
       
     5  * Redistribution and use in source and binary forms, with or without
       
     6  * modification, are permitted provided that the following conditions
       
     7  * are met:
       
     8  * 1. Redistributions of source code must retain the above copyright
       
     9  *    notice, this list of conditions and the following disclaimer.
       
    10  * 2. Redistributions in binary form must reproduce the above copyright
       
    11  *    notice, this list of conditions and the following disclaimer in the
       
    12  *    documentation and/or other materials provided with the distribution.
       
    13  *
       
    14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
       
    15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
       
    16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
       
    17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
       
    18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
       
    19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    24  */
       
    25 
       
    26 #include <sys/types.h>
       
    27 #include <sys/ioctl.h>
       
    28 
       
    29 #include <err.h>
       
    30 #include <errno.h>
       
    31 #include <signal.h>
       
    32 #include <stdio.h>
       
    33 #include <string.h>
       
    34 #include <time.h>
       
    35 #include <unistd.h>
       
    36 
       
    37 #include "ftp.h"
       
    38 
       
    39 #define DEFAULT_WINSIZE 80
       
    40 #define MAX_WINSIZE 512
       
    41 #define UPDATE_INTERVAL 1	/* update the progress meter every second */
       
    42 #define STALL_TIME 5		/* we're stalled after this many seconds */
       
    43 
       
    44 time_t	monotime(void);
       
    45 
       
    46 /* formats and inserts the specified size into the given buffer */
       
    47 static void format_size(char *, int, off_t);
       
    48 static void format_rate(char *, int, off_t);
       
    49 
       
    50 /* window resizing */
       
    51 static void sig_winch(int);
       
    52 static void setscreensize(void);
       
    53 
       
    54 /* updates the progressmeter to reflect the current state of the transfer */
       
    55 void refresh_progress_meter(void);
       
    56 
       
    57 /* signal handler for updating the progress meter */
       
    58 static void update_progress_meter(int);
       
    59 
       
    60 static const char *title;	/* short title for the start of progress bar */
       
    61 static time_t start;		/* start progress */
       
    62 static time_t last_update;	/* last progress update */
       
    63 static off_t start_pos;		/* initial position of transfer */
       
    64 static off_t end_pos;		/* ending position of transfer */
       
    65 static off_t cur_pos;		/* transfer position as of last refresh */
       
    66 static off_t offset;		/* initial offset from start_pos */
       
    67 static volatile off_t *counter;	/* progress counter */
       
    68 static long stalled;		/* how long we have been stalled */
       
    69 static int bytes_per_second;	/* current speed in bytes per second */
       
    70 static int win_size;		/* terminal window size */
       
    71 static volatile sig_atomic_t win_resized; /* for window resizing */
       
    72 static const char *filename;	/* To be displayed in non-verbose mode */
       
    73 /* units for format_size */
       
    74 static const char unit[] = " KMGT";
       
    75 
       
    76 time_t
       
    77 monotime(void)
       
    78 {
       
    79 	struct timespec ts;
       
    80 
       
    81 	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
       
    82 		err(1, "monotime");
       
    83 
       
    84 	return ts.tv_sec;
       
    85 }
       
    86 
       
    87 static void
       
    88 format_rate(char *buf, int size, off_t bytes)
       
    89 {
       
    90 	int i;
       
    91 
       
    92 	bytes *= 100;
       
    93 	for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
       
    94 		bytes = (bytes + 512) / 1024;
       
    95 	if (i == 0) {
       
    96 		i++;
       
    97 		bytes = (bytes + 512) / 1024;
       
    98 	}
       
    99 	snprintf(buf, size, "%lld.%02lld %c%s",
       
   100 	    (long long) (bytes + 5) / 100,
       
   101 	    (long long) (bytes + 5) / 10 % 10,
       
   102 	    unit[i],
       
   103 	    "B");
       
   104 }
       
   105 
       
   106 static void
       
   107 format_size(char *buf, int size, off_t bytes)
       
   108 {
       
   109 	int i;
       
   110 
       
   111 	for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
       
   112 		bytes = (bytes + 512) / 1024;
       
   113 	snprintf(buf, size, "%4lld%c%s",
       
   114 	    (long long) bytes,
       
   115 	    unit[i],
       
   116 	    i ? "B" : " ");
       
   117 }
       
   118 
       
   119 void
       
   120 refresh_progress_meter(void)
       
   121 {
       
   122 	char buf[MAX_WINSIZE + 1];
       
   123 	const char *dot = "";
       
   124 	time_t now;
       
   125 	off_t transferred, bytes_left;
       
   126 	double elapsed;
       
   127 	int len, cur_speed, hours, minutes, seconds, barlength, i;
       
   128 	int percent, overhead = 30;
       
   129 
       
   130 	transferred = *counter - (cur_pos ? cur_pos : start_pos);
       
   131 	cur_pos = *counter;
       
   132 	now = monotime();
       
   133 	bytes_left = end_pos - cur_pos;
       
   134 
       
   135 	if (bytes_left > 0)
       
   136 		elapsed = now - last_update;
       
   137 	else {
       
   138 		elapsed = now - start;
       
   139 		/* Calculate true total speed when done */
       
   140 		transferred = end_pos - start_pos;
       
   141 		bytes_per_second = 0;
       
   142 	}
       
   143 
       
   144 	/* calculate speed */
       
   145 	if (elapsed != 0)
       
   146 		cur_speed = (transferred / elapsed);
       
   147 	else
       
   148 		cur_speed = transferred;
       
   149 
       
   150 #define AGE_FACTOR 0.9
       
   151 	if (bytes_per_second != 0) {
       
   152 		bytes_per_second = (bytes_per_second * AGE_FACTOR) +
       
   153 		    (cur_speed * (1.0 - AGE_FACTOR));
       
   154 	} else
       
   155 		bytes_per_second = cur_speed;
       
   156 
       
   157 	buf[0] = '\0';
       
   158 	/* title */
       
   159 	if (!verbose && title != NULL) {
       
   160 		len = strlen(title);
       
   161 		if (len < 7)
       
   162 			len = 7;
       
   163 		else if (len > 12) {
       
   164 			len = 12;
       
   165 			dot = "...";
       
   166 			overhead += 3;
       
   167 		}
       
   168 		snprintf(buf, sizeof buf, "\r%-*.*s%s ", len, len, title, dot);
       
   169 		overhead += len + 1;
       
   170 	} else
       
   171 		snprintf(buf, sizeof buf, "\r");
       
   172 
       
   173 	if (end_pos == 0 || cur_pos == end_pos)
       
   174 		percent = 100;
       
   175 	else
       
   176 		percent = ((float)cur_pos / end_pos) * 100;
       
   177 
       
   178 	/* filename and percent */
       
   179 	if (!verbose && filename != NULL) {
       
   180 		len = strlen(filename);
       
   181 		if (len < 12)
       
   182 			len = 12;
       
   183 		else if (len > 25) {
       
   184 			len = 22;
       
   185 			dot = "...";
       
   186 			overhead += 3;
       
   187 		}
       
   188 		snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
       
   189 		    "%-*.*s%s %3d%% ", len, len, filename, dot, percent);
       
   190 		overhead += len + 1;
       
   191 	} else
       
   192 		snprintf(buf, sizeof buf, "\r%3d%% ", percent);
       
   193 
       
   194 	/* bar */
       
   195 	barlength = win_size - overhead;
       
   196 	if (barlength > 0) {
       
   197 		i = barlength * percent / 100;
       
   198 		snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
       
   199 		    "|%.*s%*s| ", i,
       
   200 		    "*******************************************************"
       
   201 		    "*******************************************************"
       
   202 		    "*******************************************************"
       
   203 		    "*******************************************************"
       
   204 		    "*******************************************************"
       
   205 		    "*******************************************************"
       
   206 		    "*******************************************************",
       
   207 		    barlength - i, "");
       
   208 
       
   209 	}
       
   210 
       
   211 	/* amount transferred */
       
   212 	format_size(buf + strlen(buf), win_size - strlen(buf), cur_pos);
       
   213 	strlcat(buf, " ", win_size);
       
   214 
       
   215 	/* ETA */
       
   216 	if (!transferred)
       
   217 		stalled += elapsed;
       
   218 	else
       
   219 		stalled = 0;
       
   220 
       
   221 	if (stalled >= STALL_TIME)
       
   222 		strlcat(buf, "- stalled -", win_size);
       
   223 	else if (bytes_per_second == 0 && bytes_left)
       
   224 		strlcat(buf, "  --:-- ETA", win_size);
       
   225 	else {
       
   226 		if (bytes_left > 0)
       
   227 			seconds = bytes_left / bytes_per_second;
       
   228 		else
       
   229 			seconds = elapsed;
       
   230 
       
   231 		hours = seconds / 3600;
       
   232 		seconds -= hours * 3600;
       
   233 		minutes = seconds / 60;
       
   234 		seconds -= minutes * 60;
       
   235 
       
   236 		if (hours != 0)
       
   237 			snprintf(buf + strlen(buf), win_size - strlen(buf),
       
   238 			    "%d:%02d:%02d", hours, minutes, seconds);
       
   239 		else
       
   240 			snprintf(buf + strlen(buf), win_size - strlen(buf),
       
   241 			    "  %02d:%02d", minutes, seconds);
       
   242 
       
   243 		if (bytes_left > 0)
       
   244 			strlcat(buf, " ETA", win_size);
       
   245 		else
       
   246 			strlcat(buf, "    ", win_size);
       
   247 	}
       
   248 
       
   249 	if (progressmeter)
       
   250 		write(STDERR_FILENO, buf, strlen(buf));
       
   251 
       
   252 	last_update = now;
       
   253 }
       
   254 
       
   255 static void
       
   256 update_progress_meter(int ignore)
       
   257 {
       
   258 	int save_errno;
       
   259 
       
   260 	save_errno = errno;
       
   261 
       
   262 	if (win_resized) {
       
   263 		setscreensize();
       
   264 		win_resized = 0;
       
   265 	}
       
   266 
       
   267 	refresh_progress_meter();
       
   268 
       
   269 	signal(SIGALRM, update_progress_meter);
       
   270 	alarm(UPDATE_INTERVAL);
       
   271 	errno = save_errno;
       
   272 }
       
   273 
       
   274 void
       
   275 start_progress_meter(const char *fn, const char *t, off_t filesize, off_t *ctr)
       
   276 {
       
   277 	start = last_update = monotime();
       
   278 	start_pos = *ctr;
       
   279 	offset = *ctr;
       
   280 	cur_pos = 0;
       
   281 	end_pos = 0;
       
   282 	counter = ctr;
       
   283 	stalled = 0;
       
   284 	bytes_per_second = 0;
       
   285 	filename = fn;
       
   286 	title = t;
       
   287 
       
   288 	/*
       
   289 	 * Suppress progressmeter if filesize isn't known when
       
   290 	 * Content-Length header has bogus values.
       
   291 	 */
       
   292 	if (filesize <= 0)
       
   293 		return;
       
   294 
       
   295 	end_pos = filesize;
       
   296 	if (progressmeter)
       
   297 		setscreensize();
       
   298 
       
   299 	refresh_progress_meter();
       
   300 
       
   301 	signal(SIGALRM, update_progress_meter);
       
   302 	signal(SIGWINCH, sig_winch);
       
   303 	alarm(UPDATE_INTERVAL);
       
   304 }
       
   305 
       
   306 void
       
   307 stop_progress_meter(void)
       
   308 {
       
   309 	char	rate_str[32];
       
   310 	double	elapsed;
       
   311 
       
   312 	alarm(0);
       
   313 
       
   314 	/* Ensure we complete the progress */
       
   315 	if (end_pos && cur_pos != end_pos)
       
   316 		refresh_progress_meter();
       
   317 
       
   318 	if (progressmeter && end_pos)
       
   319 		write(STDERR_FILENO, "\n", 1);
       
   320 
       
   321 	if (!verbose)
       
   322 		return;
       
   323 
       
   324 	elapsed = monotime() - start;
       
   325 	if (end_pos == 0) {
       
   326 		if (elapsed != 0)
       
   327 			bytes_per_second = *counter / elapsed;
       
   328 		else
       
   329 			bytes_per_second = *counter;
       
   330 	}
       
   331 
       
   332 	format_rate(rate_str, sizeof rate_str, bytes_per_second);
       
   333 	log_info("%lld byte%s received in %.2f seconds (%s/s)\n",
       
   334 	    (end_pos) ? cur_pos - offset : *counter,
       
   335 	    *counter != 1 ? "s" : "",  elapsed, rate_str);
       
   336 }
       
   337 
       
   338 static void
       
   339 sig_winch(int sig)
       
   340 {
       
   341 	win_resized = 1;
       
   342 }
       
   343 
       
   344 static void
       
   345 setscreensize(void)
       
   346 {
       
   347 	struct winsize winsize;
       
   348 
       
   349 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
       
   350 	    winsize.ws_col != 0) {
       
   351 		if (winsize.ws_col > MAX_WINSIZE)
       
   352 			win_size = MAX_WINSIZE;
       
   353 		else
       
   354 			win_size = winsize.ws_col;
       
   355 	} else
       
   356 		win_size = DEFAULT_WINSIZE;
       
   357 	win_size += 1;					/* trailing \0 */
       
   358 }