diff --git a/src/ChangeLog b/src/ChangeLog index 5b1554f8..5f64abf6 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,18 @@ +2005-06-29 Hrvoje Niksic + + * main.c (secs_to_human_time): Use print_decimal when printing + total download time in seconds. + + * progress.c (print_row_stats): Use it to print total download + time at the end of the download. + (create_image): Ditto. + + * utils.c (print_decimal): New function; print small decimal + numbers with more precision than large ones. + + * progress.c (print_row_stats): New function. Print ETA after the + download rate at the end of each row. + 2005-06-28 Hrvoje Niksic * init.c (parse_line): Check for alphanumerics. diff --git a/src/convert.c b/src/convert.c index 2cb21577..8afef4c5 100644 --- a/src/convert.c +++ b/src/convert.c @@ -168,8 +168,8 @@ convert_all_links (void) secs = ptimer_measure (timer) / 1000; ptimer_destroy (timer); - logprintf (LOG_VERBOSE, _("Converted %d files in %.*f seconds.\n"), - file_count, secs < 10 ? 3 : 1, secs); + logprintf (LOG_VERBOSE, _("Converted %d files in %s seconds.\n"), + file_count, print_decimal (secs)); } static void write_backup_file (const char *, downloaded_file_t); diff --git a/src/main.c b/src/main.c index 9bf80465..ecbd8808 100644 --- a/src/main.c +++ b/src/main.c @@ -643,16 +643,8 @@ secs_to_human_time (double interval) sprintf (buf, "%dh %dm %ds", hours, mins, secs); else if (mins) sprintf (buf, "%dm %ds", mins, secs); - else if (interval >= 10) - sprintf (buf, "%ds", secs); else - /* For very quick downloads show more exact timing information. */ - sprintf (buf, "%.*fs", - interval < 0.001 ? 0 : /* 0s instead of 0.000s */ - interval < 0.01 ? 3 : /* 0.00x */ - interval < 0.1 ? 2 : /* 0.0x */ - 1, /* 0.x, 1.x, ..., 9.x */ - interval); + sprintf (buf, "%ss", print_decimal (interval)); return buf; } diff --git a/src/progress.c b/src/progress.c index 3ebec9a6..f7cbc009 100644 --- a/src/progress.c +++ b/src/progress.c @@ -205,6 +205,7 @@ struct dot_progress { int rows; /* number of rows printed so far */ int dots; /* number of dots printed in this row */ + double last_timer_value; }; @@ -220,26 +221,28 @@ dot_create (wgint initial, wgint total) if (dp->initial_length) { int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; - int remainder = dp->initial_length % row_bytes; + int remainder = dp->initial_length % ROW_BYTES; wgint skipped = dp->initial_length - remainder; if (skipped) { - int skipped_k = skipped / 1024; /* skipped amount in K */ + wgint skipped_k = skipped / 1024; /* skipped amount in K */ int skipped_k_len = numdigit (skipped_k); - if (skipped_k_len < 5) - skipped_k_len = 5; + if (skipped_k_len < 6) + skipped_k_len = 6; /* Align the [ skipping ... ] line with the dots. To do that, insert the number of spaces equal to the number of digits in the skipped amount in K. */ - logprintf (LOG_VERBOSE, _("\n%*s[ skipping %dK ]"), - 2 + skipped_k_len, "", skipped_k); + logprintf (LOG_VERBOSE, _("\n%*s[ skipping %sK ]"), + 2 + skipped_k_len, "", + number_to_static_string (skipped_k)); } - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (skipped / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (skipped / 1024)); for (; remainder >= dot_bytes; remainder -= dot_bytes) { if (dp->dots % opt.dot_spacing == 0) @@ -250,28 +253,86 @@ dot_create (wgint initial, wgint total) assert (dp->dots < opt.dots_in_line); dp->accumulated = remainder; - dp->rows = skipped / row_bytes; + dp->rows = skipped / ROW_BYTES; } return dp; } -static void -print_percentage (wgint bytes, wgint expected) -{ - /* Round to the floor value in order to gauge how much data *has* - been retrieved. 12.8% will round to 12% because the 13% mark has - not yet been reached. 100% is only shown when done. */ - int percentage = 100.0 * bytes / expected; - logprintf (LOG_VERBOSE, "%3d%%", percentage); -} +static const char *eta_to_human_short (int, bool); + +/* Prints the stats (percentage of completion, speed, ETA) for current + row. DLTIME is the time spent downloading the data in current + row. + + #### This function is somewhat uglified by the fact that current + row and last row have somewhat different stats requirements. It + might be worthwhile to split it to two different functions. */ static void -print_download_speed (struct dot_progress *dp, wgint bytes, double dltime) +print_row_stats (struct dot_progress *dp, double dltime, bool last) { - logprintf (LOG_VERBOSE, " %7s", - retr_rate (bytes, dltime - dp->last_timer_value)); - dp->last_timer_value = dltime; + const wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; + + /* bytes_displayed is the number of bytes indicated to the user by + dots printed so far, includes the initially "skipped" amount */ + wgint bytes_displayed = dp->rows * ROW_BYTES + dp->dots * opt.dot_bytes; + + if (last) + /* For last row also count bytes accumulated after last dot */ + bytes_displayed += dp->accumulated; + + if (dp->total_length) + { + /* Round to floor value to provide gauge how much data *has* + been retrieved. 12.8% will round to 12% because the 13% mark + has not yet been reached. 100% is only shown when done. */ + int percentage = 100.0 * bytes_displayed / dp->total_length; + logprintf (LOG_VERBOSE, "%3d%%", percentage); + } + + { + static char names[] = {' ', 'K', 'M', 'G'}; + int units; + double rate; + wgint bytes_this_row; + if (!last) + bytes_this_row = ROW_BYTES; + else + /* For last row also include bytes accumulated after last dot. */ + bytes_this_row = dp->dots * opt.dot_bytes + dp->accumulated; + if (dp->rows == dp->initial_length / ROW_BYTES) + /* Don't count the portion of the row belonging to initial_length */ + bytes_this_row -= dp->initial_length % ROW_BYTES; + rate = calc_rate (bytes_this_row, dltime - dp->last_timer_value, &units); + logprintf (LOG_VERBOSE, " %4.*f%c", + rate >= 100 ? 0 : rate >= 9.995 ? 1 : 2, + rate, names[units]); + dp->last_timer_value = dltime; + } + + if (!last) + { + if (dp->total_length) + { + wgint bytes_remaining = dp->total_length - bytes_displayed; + /* The quantity downloaded in this download run. */ + wgint bytes_sofar = bytes_displayed - dp->initial_length; + double secs_sofar = dltime / 1000; + int eta = (int) (secs_sofar * bytes_remaining / bytes_sofar + 0.5); + logprintf (LOG_VERBOSE, " %s", eta_to_human_short (eta, true)); + } + } + else + { + /* When done, print the total download time */ + double secs = dltime / 1000; + if (secs >= 10) + logprintf (LOG_VERBOSE, "=%s", + eta_to_human_short ((int) (secs + 0.5), true)); + else + logprintf (LOG_VERBOSE, "=%ss", print_decimal (secs)); + } } /* Dot-progress backend for progress_update. */ @@ -281,7 +342,7 @@ dot_update (void *progress, wgint howmuch, double dltime) { struct dot_progress *dp = progress; int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; log_set_flush (false); @@ -289,7 +350,8 @@ dot_update (void *progress, wgint howmuch, double dltime) for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes) { if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (dp->rows * ROW_BYTES / 1024)); if (dp->dots % opt.dot_spacing == 0) logputs (LOG_VERBOSE, " "); @@ -298,16 +360,14 @@ dot_update (void *progress, wgint howmuch, double dltime) ++dp->dots; if (dp->dots >= opt.dots_in_line) { - wgint row_qty = row_bytes; - if (dp->rows == dp->initial_length / row_bytes) - row_qty -= dp->initial_length % row_bytes; + wgint row_qty = ROW_BYTES; + if (dp->rows == dp->initial_length / ROW_BYTES) + row_qty -= dp->initial_length % ROW_BYTES; ++dp->rows; dp->dots = 0; - if (dp->total_length) - print_percentage (dp->rows * row_bytes, dp->total_length); - print_download_speed (dp, row_qty, dltime); + print_row_stats (dp, dltime, false); } } @@ -320,35 +380,22 @@ static void dot_finish (void *progress, double dltime) { struct dot_progress *dp = progress; - int dot_bytes = opt.dot_bytes; - wgint row_bytes = opt.dot_bytes * opt.dots_in_line; + wgint ROW_BYTES = opt.dot_bytes * opt.dots_in_line; int i; log_set_flush (false); if (dp->dots == 0) - logprintf (LOG_VERBOSE, "\n%5ldK", (long) (dp->rows * row_bytes / 1024)); + logprintf (LOG_VERBOSE, "\n%6sK", + number_to_static_string (dp->rows * ROW_BYTES / 1024)); for (i = dp->dots; i < opt.dots_in_line; i++) { if (i % opt.dot_spacing == 0) logputs (LOG_VERBOSE, " "); logputs (LOG_VERBOSE, " "); } - if (dp->total_length) - { - print_percentage (dp->rows * row_bytes - + dp->dots * dot_bytes - + dp->accumulated, - dp->total_length); - } - - { - wgint row_qty = dp->dots * dot_bytes + dp->accumulated; - if (dp->rows == dp->initial_length / row_bytes) - row_qty -= dp->initial_length % row_bytes; - print_download_speed (dp, row_qty, dltime); - } + print_row_stats (dp, dltime, true); logputs (LOG_VERBOSE, "\n\n"); log_set_flush (false); @@ -704,8 +751,6 @@ update_speed_ring (struct bar_progress *bp, wgint howmuch, double dltime) #endif } -static const char *eta_to_human_short (int); - #define APPEND_LITERAL(s) do { \ memcpy (p, s, sizeof (s) - 1); \ p += sizeof (s) - 1; \ @@ -884,7 +929,7 @@ create_image (struct bar_progress *bp, double dl_total_time, bool done) /* Translation note: "ETA" is English-centric, but this must be short, ideally 3 chars. Abbreviate if necessary. */ - sprintf (p, _(" eta %s"), eta_to_human_short (eta)); + sprintf (p, _(" eta %s"), eta_to_human_short (eta, false)); move_to_end (p); } else if (bp->total_length > 0) @@ -901,15 +946,9 @@ create_image (struct bar_progress *bp, double dl_total_time, bool done) strcpy (p, _(" in ")); move_to_end (p); /* not p+=6, think translations! */ if (secs >= 10) - strcpy (p, eta_to_human_short ((int) (secs + 0.5))); + strcpy (p, eta_to_human_short ((int) (secs + 0.5), false)); else - /* For very quick downloads show more exact timing information. */ - sprintf (p, "%.*fs", - secs < 0.001 ? 0 : /* 0s instead of 0.000s */ - secs < 0.01 ? 3 : /* 0.00x */ - secs < 0.1 ? 2 : /* 0.0x */ - 1, /* 0.x, 1.x, ..., 9.x */ - secs); + sprintf (p, "%ss", print_decimal (secs)); move_to_end (p); } @@ -984,18 +1023,20 @@ progress_handle_sigwinch (int sig) and hours are shown. This ensures brevity while still displaying as much as possible. - If SEP is false, the separator between minutes and seconds (and - hours and minutes, etc.) is not included, shortening the display by - one additional character. This is used for dot progress. + If CONDENSED is true, the separator between minutes and seconds + (and hours and minutes, etc.) is not included, shortening the + display by one additional character. This is used for dot + progress. The display never occupies more than 7 characters of screen space. */ static const char * -eta_to_human_short (int secs) +eta_to_human_short (int secs, bool condensed) { static char buf[10]; /* 8 should be enough, but just in case */ static int last = -1; + const char *space = condensed ? "" : " "; /* Trivial optimization. create_image can call us every 200 msecs (see bar_update) for fast downloads, but ETA will only change @@ -1007,11 +1048,11 @@ eta_to_human_short (int secs) if (secs < 100) sprintf (buf, "%ds", secs); else if (secs < 100 * 60) - sprintf (buf, "%dm %ds", secs / 60, secs % 60); + sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60); else if (secs < 100 * 3600) - sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60); + sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60); else if (secs < 100 * 86400) - sprintf (buf, "%dd %dh", secs / 86400, (secs / 3600) % 60); + sprintf (buf, "%dd%s%dh", secs / 86400, space, (secs / 3600) % 60); else /* even (2^31-1)/86400 doesn't overflow BUF. */ sprintf (buf, "%dd", secs / 86400); diff --git a/src/utils.c b/src/utils.c index f6d708cc..9c8beb10 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2039,3 +2039,38 @@ stable_sort (void *base, size_t nmemb, size_t size, mergesort_internal (base, temp, size, 0, nmemb - 1, cmpfun); } } + +/* Print a decimal number. If it is equal to or larger than ten, the + number is rounded. Otherwise it is printed with one significant + digit without trailing zeros and with no more than three fractional + digits total. For example, 0.1 is printed as "0.1", 0.035 is + printed as "0.04", 0.0091 as "0.009", and 0.0003 as simply "0". + + This is useful for displaying durations because it provides + order-of-magnitude information without unnecessary clutter -- + long-running downloads are shown without the fractional part, and + short ones still retain one significant digit. */ + +const char * +print_decimal (double number) +{ + static char buf[32]; + double n = number >= 0 ? number : -number; + + if (n >= 9.95) + /* Cut off at 9.95 because the below %.1f would round 9.96 to + "10.0" instead of "10". OTOH 9.94 will print as "9.9". */ + snprintf (buf, sizeof buf, "%.0f", number); + else if (n >= 0.95) + snprintf (buf, sizeof buf, "%.1f", number); + else if (n >= 0.001) + snprintf (buf, sizeof buf, "%.1g", number); + else if (n >= 0.0005) + /* round [0.0005, 0.001) to 0.001 */ + snprintf (buf, sizeof buf, "%.3f", number); + else + /* print numbers close to 0 as 0, not 0.000 */ + strcpy (buf, "0"); + + return buf; +} diff --git a/src/utils.h b/src/utils.h index 007c05f1..8887e9c0 100644 --- a/src/utils.h +++ b/src/utils.h @@ -126,4 +126,6 @@ int base64_decode (const char *, char *); void stable_sort (void *, size_t, size_t, int (*) (const void *, const void *)); +const char *print_decimal (double); + #endif /* UTILS_H */