diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 090f434..1b623d5 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -20,16 +20,25 @@ extern "C" { #endif -#define PV_DISPLAY_PROGRESS 1 -#define PV_DISPLAY_TIMER 2 -#define PV_DISPLAY_ETA 4 -#define PV_DISPLAY_RATE 8 -#define PV_DISPLAY_AVERAGERATE 16 -#define PV_DISPLAY_BYTES 32 -#define PV_DISPLAY_NAME 64 -#define PV_DISPLAY_BUFPERCENT 128 -#define PV_DISPLAY_OUTPUTBUF 256 -#define PV_DISPLAY_FINETA 512 +/* + * Types of display component that make up an output string. + */ +typedef enum { + PV_COMPONENT_STRING, /* fixed string */ + PV_COMPONENT_PROGRESS, /* progress bar, with percentage if known */ + PV_COMPONENT_BYTES, /* number of bytes transferred */ + PV_COMPONENT_TIMER, /* elapsed time */ + PV_COMPONENT_RATE, /* current transfer rate */ + PV_COMPONENT_AVERAGERATE, /* average transfer rate */ + PV_COMPONENT_ETA, /* estimated time remaining until completion */ + PV_COMPONENT_FINETA, /* estimated time of completion */ + PV_COMPONENT_NAME, /* name prefix */ + PV_COMPONENT_BUFPERCENT, /* percentage of buffer used */ + PV_COMPONENT_OUTPUTBUF, /* recent bytes in output buffer */ + PV_COMPONENT__MAX +} pv_display_component; + +#define PV_SIZEOF_COMPONENT_STR 1024 /* size of buffer for each component */ #define RATE_GRANULARITY 100000000 /* nsec between -L rate chunks */ #define RATE_BURST_WINDOW 5 /* rate burst window (multiples of rate) */ @@ -43,25 +52,9 @@ extern "C" { #define MAXIMISE_BUFFER_FILL 1 - -typedef struct pvhistory { - off_t total_bytes; - long double elapsed_sec; -} pvhistory_t; - #define PV_SIZEOF_DEFAULT_FORMAT 512 #define PV_SIZEOF_CWD 4096 #define PV_SIZEOF_LASTOUTPUT_BUFFER 256 -#define PV_SIZEOF_STR_NAME 512 -#define PV_SIZEOF_STR_TRANSFERRED 128 -#define PV_SIZEOF_STR_BUFPERCENT 128 -#define PV_SIZEOF_STR_TIMER 128 -#define PV_SIZEOF_STR_RATE 128 -#define PV_SIZEOF_STR_AVERAGE_RATE 128 -#define PV_SIZEOF_STR_PROGRESS 1024 -#define PV_SIZEOF_STR_LASTOUTPUT 512 -#define PV_SIZEOF_STR_ETA 128 -#define PV_SIZEOF_STR_FINETA 128 #define PV_FORMAT_ARRAY_MAX 100 #define PV_SIZEOF_CRS_LOCK_FILE 1024 @@ -182,36 +175,39 @@ struct pvstate_s { long double prev_trans; /* Keep track of progress over last intervals to compute current average rate. */ - /*@null@*/ pvhistory_t *history; /* state at previous intervals (circular buffer) */ - unsigned int history_len; /* total size */ + /*@null@*/ struct { /* state at previous intervals (circular buffer) */ + long long total_bytes; + long double elapsed_sec; + } *history; + size_t history_len; /* total size of history array */ int history_interval; /* seconds between each history entry */ - int history_first; - int history_last; + size_t history_first; + size_t history_last; long double current_avg_rate; /* current average rate over last history intervals */ - - off_t initial_offset; - /*@only@*/ char *display_buffer; + + off_t initial_offset; /* offset when first opened (when watching fds) */ + + /*@null@*/ /*@only@*/ char *display_buffer; long display_buffer_size; size_t lastoutput_length; /* number of last-output bytes to show */ char lastoutput_buffer[PV_SIZEOF_LASTOUTPUT_BUFFER]; int prev_width; /* screen width last time we were called */ int prev_length; /* length of last string we output */ - char str_name[PV_SIZEOF_STR_NAME]; - char str_transferred[PV_SIZEOF_STR_TRANSFERRED]; - char str_bufpercent[PV_SIZEOF_STR_BUFPERCENT]; - char str_timer[PV_SIZEOF_STR_TIMER]; - char str_rate[PV_SIZEOF_STR_RATE]; - char str_average_rate[PV_SIZEOF_STR_AVERAGE_RATE]; - char str_progress[PV_SIZEOF_STR_PROGRESS]; - char str_lastoutput[PV_SIZEOF_STR_LASTOUTPUT]; - char str_eta[PV_SIZEOF_STR_ETA]; - char str_fineta[PV_SIZEOF_STR_FINETA]; - unsigned long components_used; /* bitmask of components used */ - struct { - const char *string; - int length; - } format[PV_FORMAT_ARRAY_MAX]; bool display_visible; /* set once anything written to terminal */ + + size_t format_segment_count; /* number of format string segments */ + struct { /* format string broken into display components */ + pv_display_component type; /* type of display component */ + size_t str_start; /* for strings: start offset */ + size_t str_length; /* for strings: length */ + } format[PV_FORMAT_ARRAY_MAX]; + + struct { /* display components */ + bool required; /* true if included in format */ + char content[PV_SIZEOF_COMPONENT_STR]; /* string to display */ + size_t length; /* number of bytes in string */ + } component[PV_COMPONENT__MAX]; + } display; /******************** diff --git a/src/pv/display.c b/src/pv/display.c index 766306c..ea8abbb 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -184,7 +184,13 @@ static void pv__si_prefix(long double *value, char *prefix, const long double ra * Prefix list for multiples of 1000. */ if (NULL == pfx_000) { + /*@-onlytrans@ */ pfx_000 = _("yzafpnum kMGTPEZY"); + /* + * splint: this is only looked up once in the program's run, + * so the memory leak is negligible. + */ + /*@+onlytrans@ */ if (NULL == pfx_000) { debug("%s", "prefix list was NULL"); return; @@ -196,7 +202,9 @@ static void pv__si_prefix(long double *value, char *prefix, const long double ra * Prefix list for multiples of 1024. */ if (NULL == pfx_024) { + /*@-onlytrans@ */ pfx_024 = _("yzafpnum KMGTPEZY"); + /*@+onlytrans@ *//* splint: see above. */ if (NULL == pfx_024) { debug("%s", "prefix list was NULL"); return; @@ -367,53 +375,52 @@ static void pv__sizestr(char *buffer, size_t bufsize, char *format, static void pv__format_init(pvstate_t state) { const char *formatstr; - const char *searchptr; - int strpos; - int segment; + size_t strpos; + size_t segment; if (NULL == state) return; - state->display.str_name[0] = '\0'; - state->display.str_transferred[0] = '\0'; - state->display.str_timer[0] = '\0'; - state->display.str_rate[0] = '\0'; - state->display.str_average_rate[0] = '\0'; - state->display.str_progress[0] = '\0'; - state->display.str_eta[0] = '\0'; + state->display.format_segment_count = 0; memset(state->display.format, 0, PV_FORMAT_ARRAY_MAX * sizeof(state->display.format[0])); + memset(state->display.component, 0, PV_COMPONENT__MAX * sizeof(state->display.component[0])); if (state->control.name) { - (void) pv_snprintf(state->display.str_name, PV_SIZEOF_STR_NAME, "%9.500s:", state->control.name); + (void) pv_snprintf(state->display.component[PV_COMPONENT_NAME].content, PV_SIZEOF_COMPONENT_STR, + "%9.500s:", state->control.name); + state->display.component[PV_COMPONENT_NAME].length = + strlen(state->display.component[PV_COMPONENT_NAME].content); } formatstr = state->control.format_string ? state->control.format_string : state->control.default_format; - state->display.components_used = 0; + if (NULL == formatstr) + return; /* * Split the format string into segments. Each segment consists - * of a string pointer and a length. + * of a type and some string information. * - * A length of zero indicates that the segment is a fixed-size - * component updated by pv__format(), so it is a pointer to one - * of the state->str_* buffers that pv__format() updates. + * A type of PV_COMPONENT_STRING indicates that the segment is a + * constant string starting at a position in the format string and + * with a particular length. * - * A length below zero indicates that the segment is a variable - * sized component which will be recalculated by pv__format() - * after the length of all fixed-size segments is known, and so - * the string is a pointer to another state->str_* buffer - * (currently it will only ever be state->display.str_progress). + * A type other than PV_COMPONENT_STRING indicates that the segment + * is a string updated by pv__format(), whose contents will be in + * component[type]. * - * A length above zero indicates that the segment is a constant - * string of the given length (not necessarily null terminated). + * In pv__format(), the content of a PV_COMPONENT_PROGRESS component + * is calculated after first populating all the other components + * referenced by the format segments. * - * In pv__format(), after the state->str_* buffers have all been - * filled in, the output string is generated by sticking all of + * Then, pv_format() generates the output string by sticking all of * these segments together. */ segment = 0; - for (strpos = 0; formatstr[strpos] != '\0' && segment < 99; strpos++, segment++) { + for (strpos = 0; formatstr[strpos] != '\0' && segment < PV_FORMAT_ARRAY_MAX; strpos++, segment++) { + pv_display_component seg_type; + size_t str_start, str_length; + if ('%' == formatstr[strpos]) { unsigned long number_prefix; #if HAVE_STRTOUL @@ -441,95 +448,91 @@ static void pv__format_init(pvstate_t state) } #endif /* !HAVE_STRTOUL */ + seg_type = PV_COMPONENT_STRING; + str_start = 0; + str_length = 0; + switch (formatstr[strpos]) { case 'p': - state->display.format[segment].string = state->display.str_progress; - state->display.format[segment].length = -1; - state->display.components_used |= PV_DISPLAY_PROGRESS; + seg_type = PV_COMPONENT_PROGRESS; break; case 't': - state->display.format[segment].string = state->display.str_timer; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_TIMER; + seg_type = PV_COMPONENT_TIMER; break; case 'e': - state->display.format[segment].string = state->display.str_eta; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_ETA; + seg_type = PV_COMPONENT_ETA; break; case 'I': - state->display.format[segment].string = state->display.str_fineta; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_FINETA; + seg_type = PV_COMPONENT_FINETA; break; case 'A': - state->display.format[segment].string = state->display.str_lastoutput; - state->display.format[segment].length = 0; + seg_type = PV_COMPONENT_OUTPUTBUF; if (number_prefix > PV_SIZEOF_LASTOUTPUT_BUFFER) number_prefix = PV_SIZEOF_LASTOUTPUT_BUFFER; if (number_prefix < 1) number_prefix = 1; - state->display.lastoutput_length = number_prefix; - state->display.components_used |= PV_DISPLAY_OUTPUTBUF; + state->display.lastoutput_length = (size_t) number_prefix; break; case 'r': - state->display.format[segment].string = state->display.str_rate; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_RATE; + seg_type = PV_COMPONENT_RATE; break; case 'a': - state->display.format[segment].string = state->display.str_average_rate; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_AVERAGERATE; + seg_type = PV_COMPONENT_AVERAGERATE; break; case 'b': - state->display.format[segment].string = state->display.str_transferred; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_BYTES; + seg_type = PV_COMPONENT_BYTES; break; case 'T': - state->display.format[segment].string = state->display.str_bufpercent; - state->display.format[segment].length = 0; - state->display.components_used |= PV_DISPLAY_BUFPERCENT; + seg_type = PV_COMPONENT_BUFPERCENT; break; case 'N': - state->display.format[segment].string = state->display.str_name; - state->display.format[segment].length = (int) strlen(state->display.str_name); - state->display.components_used |= PV_DISPLAY_NAME; + seg_type = PV_COMPONENT_NAME; break; case '%': /* %% => % */ - state->display.format[segment].string = &(formatstr[strpos]); - state->display.format[segment].length = 1; + seg_type = PV_COMPONENT_STRING; + str_start = strpos; + str_length = 1; break; - case 0: + case '\0': /* % at end => just % */ - state->display.format[segment].string = &(formatstr[--strpos]); - state->display.format[segment].length = 1; + seg_type = PV_COMPONENT_STRING; + str_start = strpos - 1; + str_length = 1; break; default: /* %z (unknown) => %z */ - state->display.format[segment].string = &(formatstr[--strpos]); - state->display.format[segment].length = 2; - strpos++; + seg_type = PV_COMPONENT_STRING; + str_start = strpos - 1; + str_length = 2; break; } } else { + const char *searchptr; int foundlength; + searchptr = strchr(&(formatstr[strpos]), '%'); if (NULL == searchptr) { foundlength = (int) strlen(&(formatstr[strpos])); } else { foundlength = searchptr - &(formatstr[strpos]); } - state->display.format[segment].string = &(formatstr[strpos]); - state->display.format[segment].length = foundlength; + + seg_type = PV_COMPONENT_STRING; + str_start = strpos; + str_length = (size_t) foundlength; + strpos += foundlength - 1; } - } - state->display.format[segment].string = 0; - state->display.format[segment].length = 0; + if (seg_type != PV_COMPONENT_STRING) + state->display.component[seg_type].required = true; + + state->display.format[segment].type = seg_type; + state->display.format[segment].str_start = str_start; + state->display.format[segment].str_length = str_length; + state->display.format_segment_count++; + } } /* @@ -544,8 +547,8 @@ static long bound_long(long x, long min, long max) /* Update history and current average rate */ static void update_history_avg_rate(pvstate_t state, long long total_bytes, long double elapsed_sec, long double rate) { - int first = state->display.history_first; - int last = state->display.history_last; + size_t first = state->display.history_first; + size_t last = state->display.history_last; long double last_elapsed; if (NULL == state->display.history) @@ -566,10 +569,13 @@ static void update_history_avg_rate(pvstate_t state, long long total_bytes, long * buffer. */ if (last_elapsed > 0.0) { - int len = state->display.history_len; - state->display.history_last = last = (last + 1) % len; - if (last == first) - state->display.history_first = first = (first + 1) % len; + size_t len = state->display.history_len; + last = (last + 1) % len; + state->display.history_last = last; + if (last == first) { + first = (first + 1) % len; + state->display.history_first = first; + } } state->display.history[last].elapsed_sec = elapsed_sec; @@ -603,15 +609,18 @@ static void update_history_avg_rate(pvstate_t state, long long total_bytes, long * If "total_bytes" is negative, then free all allocated memory and return * NULL. */ +/*@null@*/ static const char *pv__format(pvstate_t 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 static_portion_size; - int segment; + pv_display_component component_type; + size_t segment; int output_length; int display_string_length; + const char *formatstr; /* Quick sanity check - state must exist */ if (NULL == state) @@ -625,6 +634,11 @@ static const char *pv__format(pvstate_t state, return NULL; } + /* The format string is needed for the static segments. */ + formatstr = state->control.format_string ? state->control.format_string : state->control.default_format; + if (NULL == formatstr) + 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 @@ -674,13 +688,13 @@ static const char *pv__format(pvstate_t state, state->display.percentage += 2; if (state->display.percentage > 199) state->display.percentage = 0; - } else if (state->control.numeric || ((state->display.components_used & PV_DISPLAY_PROGRESS) != 0)) { + } else if (state->control.numeric || state->display.component[PV_COMPONENT_PROGRESS].required) { /* * 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->display.percentage = pv__calc_percentage(total_bytes, state->control.size); + state->display.percentage = pv__calc_percentage(total_bytes, (long long)(state->control.size)); } /* @@ -721,10 +735,10 @@ static const char *pv__format(pvstate_t state, numericprefix[0] = '\0'; - if ((state->display.components_used & PV_DISPLAY_TIMER) != 0) + if (state->display.component[PV_COMPONENT_TIMER].required) (void) pv_snprintf(numericprefix, sizeof(numericprefix), "%.4Lf ", elapsed_sec); - if ((state->display.components_used & PV_DISPLAY_BYTES) != 0) { + if (state->display.component[PV_COMPONENT_BYTES].required) { if (state->control.bits) { (void) pv_snprintf(state->display.display_buffer, state->display.display_buffer_size, @@ -750,209 +764,251 @@ static const char *pv__format(pvstate_t state, * to be placed in the output buffer. */ - state->display.str_transferred[0] = '\0'; - state->display.str_bufpercent[0] = '\0'; - state->display.str_timer[0] = '\0'; - state->display.str_rate[0] = '\0'; - state->display.str_average_rate[0] = '\0'; - state->display.str_progress[0] = '\0'; - state->display.str_lastoutput[0] = '\0'; - state->display.str_eta[0] = '\0'; - state->display.str_fineta[0] = '\0'; + for (component_type = 0; component_type < PV_COMPONENT__MAX; component_type++) { + char *component_content; + if (!state->display.component[component_type].required) + continue; - /* If we're showing bytes transferred, set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_BYTES) != 0) { - if (state->control.bits && !state->control.linemode) { - pv__sizestr(state->display.str_transferred, - PV_SIZEOF_STR_TRANSFERRED, "%s", (long double) total_bytes * 8, "", _("b"), - PV_TRANSFERCOUNT_BYTES); - } else { - pv__sizestr(state->display.str_transferred, - PV_SIZEOF_STR_TRANSFERRED, "%s", - (long double) total_bytes, "", _("B"), - state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); + /* + * Don't try to calculate ETA if the size is not known. We + * check here to avoid big indented blocks if we check + * later. + */ + if (state->control.size < 1 + && ((component_type == PV_COMPONENT_ETA) || (component_type == PV_COMPONENT_FINETA))) { + state->display.component[component_type].content[0] = '\0'; + state->display.component[component_type].length = 0; + continue; } - } - /* Transfer buffer percentage - set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_BUFPERCENT) != 0) { - if (state->transfer.buffer_size > 0) - (void) pv_snprintf(state->display.str_bufpercent, - PV_SIZEOF_STR_BUFPERCENT, - "{%3ld%%}", - pv__calc_percentage - (state->transfer.read_position - state->transfer.write_position, - state->transfer.buffer_size)); + component_content = state->display.component[component_type].content; + component_content[0] = '\0'; + + switch (component_type) { + + case PV_COMPONENT_STRING: + break; + + case PV_COMPONENT_PROGRESS: + /* Progress bar - variable width, so do this later. */ + break; + + case PV_COMPONENT_BYTES: + /* Bytes / bits / lines transferred. */ + if (state->control.bits && !state->control.linemode) { + pv__sizestr(component_content, PV_SIZEOF_COMPONENT_STR, "%s", + (long double) total_bytes * 8, "", _("b"), PV_TRANSFERCOUNT_BYTES); + } else { + pv__sizestr(component_content, PV_SIZEOF_COMPONENT_STR, "%s", + (long double) total_bytes, "", _("B"), + state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); + } + break; + + case PV_COMPONENT_TIMER: + /* Elapsed time. */ + /* + * 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; + + /* + * If the elapsed time is more than a day, include a day count as + * well as hours, minutes, and seconds. + */ + if (elapsed_sec > (long double) 86400.0L) { + (void) pv_snprintf(component_content, + PV_SIZEOF_COMPONENT_STR, + "%ld:%02ld:%02ld:%02ld", + ((long) elapsed_sec) / 86400, + (((long) elapsed_sec) / 3600) % + 24, (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); + } else { + (void) pv_snprintf(component_content, + PV_SIZEOF_COMPONENT_STR, + "%ld:%02ld:%02ld", + ((long) elapsed_sec) / 3600, + (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); + } + break; + + case PV_COMPONENT_RATE: + /* Current transfer rate. */ + if (state->control.bits && !state->control.linemode) { + /* bits per second */ + pv__sizestr(component_content, PV_SIZEOF_COMPONENT_STR, "[%s]", 8 * rate, "", _("b/s"), + PV_TRANSFERCOUNT_BYTES); + } else { + /* bytes or lines per second */ + pv__sizestr(component_content, PV_SIZEOF_COMPONENT_STR, + "[%s]", rate, _("/s"), _("B/s"), + state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); + } + break; + + case PV_COMPONENT_AVERAGERATE: + /* Average transfer rate. */ + if (state->control.bits && !state->control.linemode) { + /* bits per second */ + pv__sizestr(component_content, PV_SIZEOF_COMPONENT_STR, + "[%s]", 8 * average_rate, "", _("b/s"), PV_TRANSFERCOUNT_BYTES); + } else { + /* bytes or lines per second */ + pv__sizestr(component_content, + PV_SIZEOF_COMPONENT_STR, + "[%s]", average_rate, _("/s"), _("B/s"), + state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); + } + break; + + case PV_COMPONENT_ETA: + /* Estimated time remaining until completion - if size is known. */ + eta = + pv__calc_eta(total_bytes - state->display.initial_offset, + state->control.size - state->display.initial_offset, + state->display.current_avg_rate); + + /* + * Bounds check, so we don't overrun the suffix buffer. This + * means the ETA will always be less than 100,000 hours. + */ + eta = bound_long(eta, 0, (long) 360000000L); + + /* + * If the ETA is more than a day, include a day count as + * well as hours, minutes, and seconds. + */ + if (eta > 86400L) { + (void) pv_snprintf(component_content, + PV_SIZEOF_COMPONENT_STR, + "%.16s %ld:%02ld:%02ld:%02ld", + _("ETA"), eta / 86400, (eta / 3600) % 24, (eta / 60) % 60, eta % 60); + } else { + (void) pv_snprintf(component_content, + PV_SIZEOF_COMPONENT_STR, + "%.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) { + size_t erase_idx; + for (erase_idx = 0; + erase_idx < PV_SIZEOF_COMPONENT_STR && component_content[erase_idx] != '\0'; + erase_idx++) { + component_content[erase_idx] = ' '; + } + } + break; + + case PV_COMPONENT_FINETA: + /* Estimated time of completion - if size is known. */ + /* + * The ETA may be hidden by a failed ETA string + * generation. + */ + bool show_eta = true; + time_t now = time(NULL); + time_t then; + struct tm *time_ptr; + char *time_format = NULL; + + eta = + pv__calc_eta(total_bytes - state->display.initial_offset, + state->control.size - state->display.initial_offset, + state->display.current_avg_rate); + + /* + * Bounds check, so we don't overrun the suffix buffer. This + * means the ETA will always be less than 100,000 hours. + */ + eta = bound_long(eta, 0, (long) 360000000L); + + /* + * Only include the date if the ETA is more than 6 hours + * away. + */ + if (eta > (long) (6 * 3600)) { + time_format = "%Y-%m-%d %H:%M:%S"; + } else { + time_format = "%H:%M:%S"; + } + + then = now + eta; + time_ptr = localtime(&then); + + if (NULL == time_ptr) { + show_eta = false; + } else { + /* Localtime keeps data stored in a static + * buffer that gets overwritten by time + * functions. + */ + struct tm time = *time_ptr; + + (void) pv_snprintf(component_content, PV_SIZEOF_COMPONENT_STR, "%.16s ", _("ETA")); + (void) strftime(component_content + strlen(component_content), + PV_SIZEOF_COMPONENT_STR - 1 - strlen(component_content), time_format, &time); + } + + if (!show_eta) { + size_t erase_idx; + for (erase_idx = 0; + erase_idx < PV_SIZEOF_COMPONENT_STR && component_content[erase_idx] != '\0'; + erase_idx++) { + component_content[erase_idx] = ' '; + } + } + break; + + case PV_COMPONENT_NAME: + /* Name prefix. */ + if (state->control.name) { + (void) pv_snprintf(component_content, PV_SIZEOF_COMPONENT_STR, "%9.500s:", + state->control.name); + } + break; + + case PV_COMPONENT_BUFPERCENT: + /* Transfer buffer percentage utilisation. */ + if (state->transfer.buffer_size > 0) + (void) pv_snprintf(component_content, + PV_SIZEOF_COMPONENT_STR, + "{%3ld%%}", + pv__calc_percentage + (state->transfer.read_position - state->transfer.write_position, + state->transfer.buffer_size)); #ifdef HAVE_SPLICE - if (state->transfer.splice_used) - (void) pv_snprintf(state->display.str_bufpercent, PV_SIZEOF_STR_BUFPERCENT, "{%s}", "----"); + if (state->transfer.splice_used) + (void) pv_snprintf(component_content, PV_SIZEOF_COMPONENT_STR, "{%s}", "----"); #endif - } + break; - /* Timer - set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_TIMER) != 0) { - /* - * 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; - - /* - * If the elapsed time is more than a day, include a day count as - * well as hours, minutes, and seconds. - */ - if (elapsed_sec > (long double) 86400.0L) { - (void) pv_snprintf(state->display.str_timer, - PV_SIZEOF_STR_TIMER, - "%ld:%02ld:%02ld:%02ld", - ((long) elapsed_sec) / 86400, - (((long) elapsed_sec) / 3600) % - 24, (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); - } else { - (void) pv_snprintf(state->display.str_timer, - PV_SIZEOF_STR_TIMER, - "%ld:%02ld:%02ld", - ((long) elapsed_sec) / 3600, - (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); - } - } - - /* Rate - set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_RATE) != 0) { - if (state->control.bits && !state->control.linemode) { - pv__sizestr(state->display.str_rate, PV_SIZEOF_STR_RATE, "[%s]", 8 * rate, "", _("b/s"), - PV_TRANSFERCOUNT_BYTES); - } else { - pv__sizestr(state->display.str_rate, - PV_SIZEOF_STR_RATE, "[%s]", rate, _("/s"), _("B/s"), - state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); - } - } - - /* Average rate - set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_AVERAGERATE) != 0) { - if (state->control.bits && !state->control.linemode) { - pv__sizestr(state->display.str_average_rate, - PV_SIZEOF_STR_AVERAGE_RATE, "[%s]", 8 * average_rate, "", _("b/s"), - PV_TRANSFERCOUNT_BYTES); - } else { - pv__sizestr(state->display.str_average_rate, - PV_SIZEOF_STR_AVERAGE_RATE, - "[%s]", average_rate, _("/s"), _("B/s"), - state->control.linemode ? PV_TRANSFERCOUNT_LINES : PV_TRANSFERCOUNT_BYTES); - } - } - - /* Last output bytes - set up the display string. */ - if ((state->display.components_used & PV_DISPLAY_OUTPUTBUF) != 0) { - int idx; - for (idx = 0; idx < state->display.lastoutput_length; idx++) { - int c; - c = state->display.lastoutput_buffer[idx]; - state->display.str_lastoutput[idx] = isprint(c) ? c : '.'; - } - state->display.str_lastoutput[idx] = '\0'; - } - - /* ETA (only if size is known) - set up the display string. */ - if (((state->display.components_used & PV_DISPLAY_ETA) != 0) - && (state->control.size > 0)) { - eta = - pv__calc_eta(total_bytes - state->display.initial_offset, - state->control.size - state->display.initial_offset, state->display.current_avg_rate); - - /* - * Bounds check, so we don't overrun the suffix buffer. This - * means the ETA will always be less than 100,000 hours. - */ - eta = bound_long(eta, 0, (long) 360000000L); - - /* - * If the ETA is more than a day, include a day count as - * well as hours, minutes, and seconds. - */ - if (eta > 86400L) { - (void) pv_snprintf(state->display.str_eta, - PV_SIZEOF_STR_ETA, - "%.16s %ld:%02ld:%02ld:%02ld", - _("ETA"), eta / 86400, (eta / 3600) % 24, (eta / 60) % 60, eta % 60); - } else { - (void) pv_snprintf(state->display.str_eta, - PV_SIZEOF_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) { - unsigned int i; - for (i = 0; i < PV_SIZEOF_STR_ETA && state->display.str_eta[i] != '\0'; i++) { - state->display.str_eta[i] = ' '; + case PV_COMPONENT_OUTPUTBUF: + /* Recently transferred bytes. */ + size_t buf_idx; + for (buf_idx = 0; buf_idx < state->display.lastoutput_length; buf_idx++) { + int display_char; + display_char = (int) (state->display.lastoutput_buffer[buf_idx]); + component_content[buf_idx] = isprint(display_char) ? (char) display_char : '.'; } + component_content[buf_idx] = '\0'; + break; + + default: + break; } + + /* Record the string length for this component. */ + state->display.component[component_type].length = strlen(component_content); } - /* ETA as clock time (as above) - set up the display string. */ - if (((state->display.components_used & PV_DISPLAY_FINETA) != 0) - && (state->control.size > 0)) { - /* - * The ETA may be hidden by a failed ETA string - * generation. - */ - int show_eta = 1; - time_t now = time(NULL); - time_t then; - struct tm *time_ptr; - char *time_format = NULL; - - eta = - pv__calc_eta(total_bytes - state->display.initial_offset, - state->control.size - state->display.initial_offset, state->display.current_avg_rate); - - /* - * Bounds check, so we don't overrun the suffix buffer. This - * means the ETA will always be less than 100,000 hours. - */ - eta = bound_long(eta, 0, (long) 360000000L); - - /* - * Only include the date if the ETA is more than 6 hours - * away. - */ - if (eta > (long) (6 * 3600)) { - time_format = "%Y-%m-%d %H:%M:%S"; - } else { - time_format = "%H:%M:%S"; - } - - then = now + eta; - time_ptr = localtime(&then); - - if (NULL == time_ptr) { - show_eta = 0; - } else { - /* Localtime keeps data stored in a - * static buffer that gets overwritten - * by time functions. */ - struct tm time = *time_ptr; - - (void) pv_snprintf(state->display.str_fineta, PV_SIZEOF_STR_FINETA, "%.16s ", _("ETA")); - strftime(state->display.str_fineta + - strlen(state->display.str_fineta), - PV_SIZEOF_STR_FINETA - 1 - strlen(state->display.str_fineta), time_format, &time); - } - - if (!show_eta) { - unsigned int i; - for (i = 0; i < PV_SIZEOF_STR_FINETA && state->display.str_fineta[i] != '\0'; i++) { - state->display.str_fineta[i] = ' '; - } - } - } /* * Now go through all the static portions of the format to work @@ -960,13 +1016,11 @@ static const char *pv__format(pvstate_t state, * (i.e. the progress bar). */ static_portion_size = 0; - for (segment = 0; state->display.format[segment].string; segment++) { - if (state->display.format[segment].length < 0) { - continue; - } else if (state->display.format[segment].length > 0) { - static_portion_size += state->display.format[segment].length; - } else { - static_portion_size += strlen(state->display.format[segment].string); + for (segment = 0; segment < state->display.format_segment_count; segment++) { + if (state->display.format[segment].type == PV_COMPONENT_STRING) { + static_portion_size += state->display.format[segment].str_length; + } else if (state->display.format[segment].type != PV_COMPONENT_PROGRESS) { + static_portion_size += state->display.component[state->display.format[segment].type].length; } } @@ -975,11 +1029,15 @@ static const char *pv__format(pvstate_t state, /* * Assemble the progress bar now we know how big it should be. */ - if ((state->display.components_used & PV_DISPLAY_PROGRESS) != 0) { + if (state->display.component[PV_COMPONENT_PROGRESS].required) { + char *component_content; char pct[16]; int available_width, i; - strcpy(state->display.str_progress, "["); + component_content = state->display.component[PV_COMPONENT_PROGRESS].content; + component_content[0] = '\0'; + + (void) pv_snprintf(component_content, PV_SIZEOF_COMPONENT_STR, "["); if (state->control.size > 0) { if (state->display.percentage < 0) @@ -993,22 +1051,22 @@ static const char *pv__format(pvstate_t state, if (available_width < 0) available_width = 0; - if (available_width > (int) (PV_SIZEOF_STR_PROGRESS) - 16) - available_width = PV_SIZEOF_STR_PROGRESS - 16; + if (available_width > (int) (PV_SIZEOF_COMPONENT_STR) - 16) + available_width = PV_SIZEOF_COMPONENT_STR - 16; for (i = 0; i < (available_width * state->display.percentage) / 100 - 1; i++) { if (i < available_width) - (void) pv_strlcat(state->display.str_progress, "=", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, "=", PV_SIZEOF_COMPONENT_STR); } if (i < available_width) { - (void) pv_strlcat(state->display.str_progress, ">", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, ">", PV_SIZEOF_COMPONENT_STR); i++; } for (; i < available_width; i++) { - (void) pv_strlcat(state->display.str_progress, " ", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, " ", PV_SIZEOF_COMPONENT_STR); } - (void) pv_strlcat(state->display.str_progress, "] ", PV_SIZEOF_STR_PROGRESS); - (void) pv_strlcat(state->display.str_progress, pct, PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, "] ", PV_SIZEOF_COMPONENT_STR); + (void) pv_strlcat(component_content, pct, PV_SIZEOF_COMPONENT_STR); } else { int p = state->display.percentage; @@ -1017,8 +1075,8 @@ static const char *pv__format(pvstate_t state, if (available_width < 0) available_width = 0; - if (available_width > (int) (PV_SIZEOF_STR_PROGRESS) - 16) - available_width = PV_SIZEOF_STR_PROGRESS - 16; + if (available_width > (int) (PV_SIZEOF_COMPONENT_STR) - 16) + available_width = PV_SIZEOF_COMPONENT_STR - 16; debug("available_width: %d", available_width); @@ -1026,20 +1084,23 @@ static const char *pv__format(pvstate_t state, p = 200 - p; for (i = 0; i < (available_width * p) / 100; i++) { if (i < available_width) - (void) pv_strlcat(state->display.str_progress, " ", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, " ", PV_SIZEOF_COMPONENT_STR); } - (void) pv_strlcat(state->display.str_progress, "<=>", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, "<=>", PV_SIZEOF_COMPONENT_STR); for (; i < available_width; i++) { - (void) pv_strlcat(state->display.str_progress, " ", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, " ", PV_SIZEOF_COMPONENT_STR); } - (void) pv_strlcat(state->display.str_progress, "]", PV_SIZEOF_STR_PROGRESS); + (void) pv_strlcat(component_content, "]", PV_SIZEOF_COMPONENT_STR); } /* * If the progress bar won't fit, drop it. */ - if (strlen(state->display.str_progress) + static_portion_size > state->control.width) - state->display.str_progress[0] = '\0'; + if (strlen(component_content) + static_portion_size > state->control.width) + component_content[0] = '\0'; + + /* Record the string length for this component. */ + state->display.component[PV_COMPONENT_PROGRESS].length = strlen(component_content); } /* @@ -1048,28 +1109,38 @@ static const char *pv__format(pvstate_t state, state->display.display_buffer[0] = '\0'; display_string_length = 0; - for (segment = 0; state->display.format[segment].string; segment++) { - int segment_length; - if (state->display.format[segment].length > 0) { - segment_length = state->display.format[segment].length; + for (segment = 0; segment < state->display.format_segment_count; segment++) { + const char *segment_content; + size_t segment_length; + + if (state->display.format[segment].type == PV_COMPONENT_STRING) { + segment_content = &(formatstr[state->display.format[segment].str_start]); + segment_length = state->display.format[segment].str_length; } else { - segment_length = strlen(state->display.format[segment].string); + segment_content = state->display.component[state->display.format[segment].type].content; + segment_length = state->display.component[state->display.format[segment].type].length; } - /* Skip empty segments */ + + /* Skip empty segments. */ if (segment_length == 0) continue; + /* - * Truncate segment if it would make the display string - * overflow the buffer + * Truncate the segment if it would make the display string + * overflow the buffer. */ if (segment_length + display_string_length > state->display.display_buffer_size - 2) segment_length = state->display.display_buffer_size - display_string_length - 2; if (segment_length < 1) break; - /* Skip segment if it would make the display too wide */ + + /* Skip the segment if it would make the display too wide. */ if (segment_length + display_string_length > (int) (state->control.width)) break; - strncat(state->display.display_buffer, state->display.format[segment].string, segment_length); + + /* Append the segment to the output string. */ + strncat(state->display.display_buffer, segment_content, segment_length); + display_string_length += segment_length; } diff --git a/src/pv/transfer.c b/src/pv/transfer.c index 66a8546..5c13bf3 100644 --- a/src/pv/transfer.c +++ b/src/pv/transfer.c @@ -695,8 +695,7 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long * If we're monitoring the output, update our copy of the * last few bytes we've written. */ - if (((state->display.components_used & PV_DISPLAY_OUTPUTBUF) != 0) - && (nwritten > 0)) { + if (state->display.component[PV_COMPONENT_OUTPUTBUF].required && (nwritten > 0)) { size_t new_portion_length, old_portion_length; new_portion_length = (size_t) nwritten;