summaryrefslogtreecommitdiff
path: root/src/pv/display.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pv/display.c')
-rw-r--r--src/pv/display.c635
1 files changed, 635 insertions, 0 deletions
diff --git a/src/pv/display.c b/src/pv/display.c
new file mode 100644
index 0000000..5a39bf7
--- /dev/null
+++ b/src/pv/display.c
@@ -0,0 +1,635 @@
+/*
+ * Display functions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+
+/*
+ * Fill in opts->width and opts->height with the current terminal size,
+ * if possible.
+ */
+void pv_screensize(opts_t opts)
+{
+#ifdef TIOCGWINSZ
+ struct winsize wsz;
+
+ if (isatty(STDERR_FILENO)) {
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &wsz) == 0) {
+ opts->width = wsz.ws_col;
+ opts->height = wsz.ws_row;
+ }
+ }
+#endif
+}
+
+
+/*
+ * Calculate the percentage transferred so far and return it.
+ */
+static long pv__calc_percentage(long long so_far, const long long total)
+{
+ if (total < 1)
+ return 0;
+
+ so_far *= 100;
+ so_far /= total;
+
+ return (long) so_far;
+}
+
+
+/*
+ * Given how many bytes have been transferred, the total byte count to
+ * transfer, and how long it's taken so far in seconds, return the estimated
+ * number of seconds until completion.
+ */
+static long pv__calc_eta(const long long so_far, const long long total,
+ const long elapsed)
+{
+ long long amount_left;
+
+ if (so_far < 1)
+ return 0;
+
+ amount_left = total - so_far;
+ amount_left *= (long long) elapsed;
+ amount_left /= so_far;
+
+ return (long) amount_left;
+}
+
+/*
+ * Given a long double value, it is divided or multiplied by the ratio until
+ * a value in the range 1.0 to 999.999... is found. The string "prefix" to
+ * is updated to the corresponding SI prefix.
+ *
+ * If "is_bytes" is 1, then the second byte of "prefix" is set to "i" to
+ * denote MiB etc (IEEE1541). Thus "prefix" should be at least 3 bytes long
+ * (to include the terminating null).
+ *
+ * Submitted by Henry Gebhardt <hsggebhardt@googlemail.com> and then
+ * modified. Further changed after input from Thomas Rachel.
+ */
+static void pv__si_prefix(long double *value, char *prefix,
+ const long double ratio, int is_bytes)
+{
+ static char *pfx = NULL;
+ static char const *pfx_middle = NULL;
+ char const *i;
+ long double cutoff;
+
+ if (pfx == NULL) {
+ pfx = _("yzafpnum kMGTPEZY");
+ }
+
+ if (pfx_middle == NULL) {
+ /*
+ * We can't assign this in the declaration above because
+ * that wouldn't be constant, so we do it here.
+ */
+ pfx_middle = strchr(pfx, ' ');
+ }
+ i = pfx_middle;
+
+ prefix[0] = ' '; /* Make the prefix start blank. */
+ prefix[1] = 0;
+
+ /*
+ * Force an empty prefix if the value is zero to avoid "0yB".
+ */
+ if (*value == 0.0)
+ return;
+
+ cutoff = ratio * 0.97;
+
+ while ((*value > cutoff) && (*(i += 1) != '\0')) {
+ *value /= ratio;
+ prefix[0] = *i;
+ }
+
+ while ((*value < 1.0) && ((i -= 1) != (pfx - 1))) {
+ *value *= ratio;
+ prefix[0] = *i;
+ }
+
+ if (is_bytes && prefix[0] != ' ') {
+ prefix[1] = 'i';
+ prefix[2] = 0;
+ }
+}
+
+
+/*
+ * Put a string in "buffer" (max length "bufsize") containing "amount"
+ * formatted such that it's 3 or 4 digits followed by an SI suffix and then
+ * whichever of "suffix_basic" or "suffix_bytes" is appropriate (whether
+ * "is_bytes" is 0 for non-byte amounts or 1 for byte amounts). If
+ * "is_bytes" is 1 then the SI units are KiB, MiB etc and the divisor is
+ * 1024 instead of 1000.
+ *
+ * The "format" string is in sprintf format and must contain exactly one %
+ * parameter (a %s) which will expand to the string described above.
+ */
+static void pv__sizestr(char *buffer, int bufsize, char *format,
+ long double amount, char *suffix_basic,
+ char *suffix_bytes, int is_bytes)
+{
+ char sizestr_buffer[256]; /* RATS: ignore (big enough) */
+ char si_prefix[8] = " "; /* RATS: ignore (big enough) */
+ long double divider;
+ long double display_amount;
+ char *suffix;
+
+ if (is_bytes) {
+ suffix = suffix_bytes;
+ divider = 1024.0;
+ } else {
+ suffix = suffix_basic;
+ divider = 1000.0;
+ }
+
+ display_amount = amount;
+
+ pv__si_prefix(&display_amount, si_prefix, divider, is_bytes);
+
+ /* Make sure we don't overrun our buffer. */
+ if (display_amount > 100000)
+ display_amount = 100000;
+
+ /* Fix for display of "1.01e+03" instead of "1010" */
+ if (display_amount > 99.9) {
+ sprintf(sizestr_buffer, "%4ld%.2s%.16s",
+ (long) display_amount, si_prefix, suffix);
+ } else {
+ /*
+ * AIX blows up with %4.3Lg%.2s%.16s for some reason, so we
+ * write display_amount separately first.
+ */
+ char str_disp[64];
+ sprintf(str_disp, "%4.3Lg", display_amount);
+ sprintf(sizestr_buffer, "%s%.2s%.16s", str_disp,
+ si_prefix, suffix);
+ }
+
+ snprintf(buffer, bufsize, format, sizestr_buffer);
+}
+
+
+/*
+ * Structure to hold the internal data for a single display.
+ */
+struct pv_display_state {
+ long percentage;
+ long double prev_elapsed_sec;
+ long double prev_rate;
+ long double prev_trans;
+ char *outbuffer;
+ long outbufsize;
+ int prev_width; /* screen width last time we were called */
+ int prev_length; /* length of last string we output */
+ opts_t opts;
+};
+
+
+/*
+ * Initialise the given display structure from the given option set. Note
+ * that this copies the given "opts" pointer, not the underlying structure,
+ * so if "opts" is freed, the state's copy is invalidated and must not be
+ * used.
+ */
+static void pv__state_init(struct pv_display_state *state, opts_t opts)
+{
+ if (state == NULL)
+ return;
+ memset(state, 0, sizeof(struct pv_display_state));
+ state->opts = opts;
+}
+
+
+/*
+ * Return a pointer to a string (which must not be freed), containing status
+ * information formatted according to the state held within the given
+ * structure, where "elapsed_sec" is the seconds elapsed since the transfer
+ * started, "bytes_since_last" is the number of bytes transferred since the
+ * last update, and "total_bytes" is the total number of bytes transferred
+ * so far.
+ *
+ * If "bytes_since_last" is negative, this is the final update so the rate
+ * is given as an an average over the whole transfer; otherwise the current
+ * rate is shown.
+ *
+ * In line mode, "bytes_since_last" and "total_bytes" are in lines, not bytes.
+ *
+ * If "total_bytes" is negative, then free all allocated memory and return
+ * NULL.
+ */
+static char *pv__format(struct pv_display_state *state,
+ long double elapsed_sec,
+ long long bytes_since_last, long long total_bytes)
+{
+ long double time_since_last, rate, average_rate;
+ long eta;
+ int component_count;
+ int static_portion_size;
+ char str_transferred[128]; /* RATS: ignore (big enough) */
+ char str_timer[128]; /* RATS: ignore (big enough) */
+ char str_rate[128]; /* RATS: ignore (big enough) */
+ char str_average_rate[128]; /* RATS: ignore (big enough) */
+ char str_eta[128]; /* RATS: ignore (big enough) */
+ int output_length;
+
+ /* Quick sanity check - state must exist */
+ if (state == NULL)
+ return NULL;
+
+ /* Negative total transfer - free memory and exit */
+ if (total_bytes < 0) {
+ if (state->outbuffer)
+ free(state->outbuffer);
+ state->outbuffer = NULL;
+ return NULL;
+ }
+
+ /*
+ * In case the time since the last update is very small, we keep
+ * track of amount transferred since the last update, and just keep
+ * adding to that until a reasonable amount of time has passed to
+ * avoid rate spikes or division by zero.
+ */
+ time_since_last = elapsed_sec - state->prev_elapsed_sec;
+ if (time_since_last <= 0.01) {
+ rate = state->prev_rate;
+ state->prev_trans += bytes_since_last;
+ } else {
+ rate =
+ ((long double) bytes_since_last +
+ state->prev_trans) / time_since_last;
+ state->prev_elapsed_sec = elapsed_sec;
+ state->prev_trans = 0;
+ }
+ state->prev_rate = rate;
+
+ /*
+ * We only calculate the overall average rate if this is the last
+ * update or if the average rate display is enabled. Otherwise it's
+ * not worth the extra CPU cycles.
+ */
+ if ((bytes_since_last < 0) || (state->opts->average_rate)) {
+ /* Sanity check to avoid division by zero */
+ if (elapsed_sec < 0.000001)
+ elapsed_sec = 0.000001;
+ average_rate =
+ ((long double) total_bytes) /
+ (long double) elapsed_sec;
+ if (bytes_since_last < 0)
+ rate = average_rate;
+ }
+
+ if (state->opts->size <= 0) {
+ /*
+ * If we don't know the total size of the incoming data,
+ * then for a percentage, we gradually increase the
+ * percentage completion as data arrives, to a maximum of
+ * 200, then reset it - we use this if we can't calculate
+ * it, so that the numeric percentage output will go
+ * 0%-100%, 100%-0%, 0%-100%, and so on.
+ */
+ if (rate > 0)
+ state->percentage += 2;
+ if (state->percentage > 199)
+ state->percentage = 0;
+ } else if (state->opts->numeric || state->opts->progress) {
+ /*
+ * If we do know the total size, and we're going to show
+ * the percentage (numeric mode or a progress bar),
+ * calculate the percentage completion.
+ */
+ state->percentage =
+ pv__calc_percentage(total_bytes, state->opts->size);
+ }
+
+ /*
+ * Reallocate output buffer if width changes.
+ */
+ if (state->outbuffer != NULL
+ && state->outbufsize < (state->opts->width * 2)) {
+ free(state->outbuffer);
+ state->outbuffer = NULL;
+ state->outbufsize = 0;
+ }
+
+ /*
+ * Allocate output buffer if there isn't one.
+ */
+ if (state->outbuffer == NULL) {
+ state->outbufsize = (2 * state->opts->width) + 80;
+ if (state->opts->name)
+ state->outbufsize += strlen(state->opts->name); /* RATS: ignore */
+ state->outbuffer = malloc(state->outbufsize + 16);
+ if (state->outbuffer == NULL) {
+ fprintf(stderr, "%s: %s: %s\n",
+ state->opts->program_name,
+ _("buffer allocation failed"),
+ strerror(errno));
+ state->opts->exit_status |= 64;
+ return NULL;
+ }
+ state->outbuffer[0] = 0;
+ }
+
+ /* In numeric output mode, our output is just a number. */
+ if (state->opts->numeric) {
+ if (state->percentage > 100) {
+ /* As mentioned above, we go 0-100, then 100-0. */
+ sprintf(state->outbuffer, "%ld\n",
+ 200 - state->percentage);
+ } else {
+ sprintf(state->outbuffer, "%ld\n",
+ state->percentage);
+ }
+ return state->outbuffer;
+ }
+
+ /*
+ * First, work out what components we will be putting in the output
+ * buffer, and for those that don't depend on the total width
+ * available (i.e. all but the progress bar), prepare their strings
+ * to be placed in the output buffer.
+ */
+
+ /* We start off with no components. */
+ component_count = 0;
+ static_portion_size = 0;
+ str_transferred[0] = 0;
+ str_timer[0] = 0;
+ str_rate[0] = 0;
+ str_average_rate[0] = 0;
+ str_eta[0] = 0;
+
+ /* If we're showing a name, add it to the list and the length. */
+ if (state->opts->name) {
+ int name_length;
+
+ name_length = strlen(state->opts->name);
+ if (name_length < 9)
+ name_length = 9;
+ if (name_length > 500)
+ name_length = 500;
+
+ component_count++;
+ static_portion_size += name_length + 1; /* +1 for ":" */
+ }
+
+ /* If we're showing bytes transferred, set up the display string. */
+ if (state->opts->bytes) {
+ pv__sizestr(str_transferred, sizeof(str_transferred), "%s",
+ (long double) total_bytes, "", _("B"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_transferred);
+ }
+
+ /* Timer - set up the display string. */
+ if (state->opts->timer) {
+ /*
+ * Bounds check, so we don't overrun the prefix buffer. This
+ * does mean that the timer will stop at a 100,000 hours,
+ * but since that's 11 years, it shouldn't be a problem.
+ */
+ if (elapsed_sec > (long double) 360000000.0L)
+ elapsed_sec = (long double) 360000000.0L;
+
+ sprintf(str_timer, "%ld:%02ld:%02ld",
+ ((long) elapsed_sec) / 3600,
+ (((long) elapsed_sec) / 60) % 60,
+ ((long) elapsed_sec) % 60);
+
+ component_count++;
+ static_portion_size += strlen(str_timer);
+ }
+
+ /* Rate - set up the display string. */
+ if (state->opts->rate) {
+ pv__sizestr(str_rate, sizeof(str_rate), "[%s]", rate,
+ _("/s"), _("B/s"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_rate);
+ }
+
+ /* Average rate - set up the display string. */
+ if (state->opts->average_rate) {
+ pv__sizestr(str_average_rate, sizeof(str_average_rate),
+ "[%s]", average_rate, _("/s"), _("B/s"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_average_rate);
+ }
+
+ /* ETA (only if size is known) - set up the display string. */
+ if (state->opts->eta && state->opts->size > 0) {
+ eta =
+ pv__calc_eta(total_bytes, state->opts->size,
+ elapsed_sec);
+
+ if (eta < 0)
+ eta = 0;
+
+ /*
+ * Bounds check, so we don't overrun the suffix buffer. This
+ * means the ETA will always be less than 100,000 hours.
+ */
+ if (eta > (long) 360000000L)
+ eta = (long) 360000000L;
+
+ sprintf(str_eta, "%.16s %ld:%02ld:%02ld", _("ETA"),
+ eta / 3600, (eta / 60) % 60, eta % 60);
+
+ /*
+ * If this is the final update, show a blank space where the
+ * ETA used to be.
+ */
+ if (bytes_since_last < 0) {
+ int i;
+ for (i = 0; i < sizeof(str_eta) && str_eta[i] != 0;
+ i++) {
+ str_eta[i] = ' ';
+ }
+ }
+
+ component_count++;
+ static_portion_size += strlen(str_eta);
+ }
+
+ /*
+ * We now have all the static portions built; all that is left is
+ * the dynamically sized progress bar. So now we assemble the
+ * output buffer, inserting the progress bar at the appropriate
+ * point with the appropriate width.
+ */
+
+ state->outbuffer[0] = 0;
+
+ if (state->opts->name) {
+ sprintf(state->outbuffer, "%9s:", state->opts->name); /* RATS: ignore (OK) */
+ }
+#define PV_APPEND(x) if (x[0] != 0) { \
+ if (state->outbuffer[0] != 0) \
+ strcat(state->outbuffer, " "); \
+ strcat(state->outbuffer, x); \
+ }
+
+ PV_APPEND(str_transferred);
+ PV_APPEND(str_timer);
+ PV_APPEND(str_rate);
+ PV_APPEND(str_average_rate);
+
+ if (state->opts->progress) {
+ char pct[16]; /* RATS: ignore (big enough) */
+ int available_width, i;
+
+ if (state->outbuffer[0] != 0)
+ strcat(state->outbuffer, " ");
+ strcat(state->outbuffer, "[");
+
+ if (state->opts->size > 0) {
+ if (state->percentage < 0)
+ state->percentage = 0;
+ if (state->percentage > 100000)
+ state->percentage = 100000;
+ sprintf(pct, "%2ld%%", state->percentage);
+ available_width =
+ state->opts->width - static_portion_size -
+ component_count - strlen(pct) - 3;
+
+ for (i = 0;
+ i <
+ (available_width * state->percentage) / 100 -
+ 1; i++) {
+ if (i < available_width)
+ strcat(state->outbuffer, "=");
+ }
+ if (i < available_width) {
+ strcat(state->outbuffer, ">");
+ i++;
+ }
+ for (; i < available_width; i++) {
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "] ");
+ strcat(state->outbuffer, pct); /* RATS: ignore (OK) */
+ } else {
+ int p = state->percentage;
+ available_width =
+ state->opts->width - static_portion_size -
+ component_count - 5;
+ if (p > 100)
+ p = 200 - p;
+ for (i = 0; i < (available_width * p) / 100; i++) {
+ if (i < available_width)
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "<=>");
+ for (; i < available_width; i++) {
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "]");
+ }
+ }
+
+ PV_APPEND(str_eta);
+
+ /*
+ * If the size of our output shrinks, we need to keep appending
+ * spaces at the end, so that we don't leave dangling bits behind.
+ */
+ output_length = strlen(state->outbuffer);
+ if ((output_length < state->prev_length)
+ && (state->opts->width >= state->prev_width)) {
+ char spaces[32]; /* RATS: ignore (bounded below) */
+ int spaces_to_add;
+ spaces_to_add = state->prev_length - output_length;
+ /* Upper boundary on number of spaces */
+ if (spaces_to_add > 15) {
+ spaces_to_add = 15;
+ }
+ output_length += spaces_to_add;
+ spaces[spaces_to_add] = 0;
+ while (--spaces_to_add >= 0) {
+ spaces[spaces_to_add] = ' ';
+ }
+ strcat(state->outbuffer, /* RATS: ignore (OK) */ spaces);
+ }
+ state->prev_width = state->opts->width;
+ state->prev_length = output_length;
+
+ return state->outbuffer;
+}
+
+
+/*
+ * Output status information on standard error, where "esec" is the seconds
+ * elapsed since the transfer started, "sl" is the number of bytes transferred
+ * since the last update, and "tot" is the total number of bytes transferred
+ * so far.
+ *
+ * If "sl" is negative, this is the final update so the rate is given as an
+ * an average over the whole transfer; otherwise the current rate is shown.
+ *
+ * In line mode, "sl" and "tot" are in lines, not bytes.
+ *
+ * If "opts" is NULL, then free all allocated memory and return.
+ */
+void pv_display(opts_t opts, long double esec, long long sl, long long tot)
+{
+ static struct pv_display_state state;
+ static int initialised = 0;
+ char *display;
+
+ if (!initialised) {
+ pv__state_init(&state, opts);
+ initialised = 1;
+ }
+
+ if (opts == NULL) {
+ if (initialised)
+ (void) pv__format(&state, 0, 0, -1);
+ initialised = 0;
+ return;
+ }
+
+ pv_sig_checkbg();
+
+ display = pv__format(&state, esec, sl, tot);
+ if (display == NULL)
+ return;
+
+ if (opts->numeric) {
+ write(STDERR_FILENO, display, strlen(display)); /* RATS: ignore */
+ } else if (opts->cursor) {
+ pv_crs_update(opts, display);
+ } else {
+ write(STDERR_FILENO, display, strlen(display)); /* RATS: ignore */
+ write(STDERR_FILENO, "\r", 1);
+ }
+}
+
+/* EOF */