📄 progress.c
字号:
} /* Store "recent" bytes and download time to history ring at the position POS. */ /* To correctly maintain the totals, first invalidate existing data (least recent in time) at this position. */ hist->total_time -= hist->times[hist->pos]; hist->total_bytes -= hist->bytes[hist->pos]; /* Now store the new data and update the totals. */ hist->times[hist->pos] = recent_age; hist->bytes[hist->pos] = bp->recent_bytes; hist->total_time += recent_age; hist->total_bytes += bp->recent_bytes; /* Start a new "recent" period. */ bp->recent_start = dltime; bp->recent_bytes = 0; /* Advance the current ring position. */ if (++hist->pos == DLSPEED_HISTORY_SIZE) hist->pos = 0;#if 0 /* Sledgehammer check to verify that the totals are accurate. */ { int i; double sumt = 0, sumb = 0; for (i = 0; i < DLSPEED_HISTORY_SIZE; i++) { sumt += hist->times[i]; sumb += hist->bytes[i]; } assert (sumb == hist->total_bytes); /* We can't use assert(sumt==hist->total_time) because some precision is lost by adding and subtracting floating-point numbers. But during a download this precision should not be detectable, i.e. no larger than 1ns. */ double diff = sumt - hist->total_time; if (diff < 0) diff = -diff; assert (diff < 1e-9); }#endif}#define APPEND_LITERAL(s) do { \ memcpy (p, s, sizeof (s) - 1); \ p += sizeof (s) - 1; \} while (0)/* Use move_to_end (s) to get S to point the end of the string (the terminating \0). This is faster than s+=strlen(s), but some people are confused when they see strchr (s, '\0') in the code. */#define move_to_end(s) s = strchr (s, '\0');#ifndef MAX# define MAX(a, b) ((a) >= (b) ? (a) : (b))#endifstatic voidcreate_image (struct bar_progress *bp, double dl_total_time, bool done){ char *p = bp->buffer; wgint size = bp->initial_length + bp->count; const char *size_grouped = with_thousand_seps (size); int size_grouped_len = strlen (size_grouped); struct bar_progress_hist *hist = &bp->hist; /* The progress bar should look like this: xx% [=======> ] nn,nnn 12.34K/s eta 36m 51s Calculate the geometry. The idea is to assign as much room as possible to the progress bar. The other idea is to never let things "jitter", i.e. pad elements that vary in size so that their variance does not affect the placement of other elements. It would be especially bad for the progress bar to be resized randomly. "xx% " or "100%" - percentage - 4 chars "[]" - progress bar decorations - 2 chars " nnn,nnn,nnn" - downloaded bytes - 12 chars or very rarely more " 12.5K/s" - download rate - 8 chars " eta 36m 51s" - ETA - 13 chars "=====>..." - progress bar - the rest */ int dlbytes_size = 1 + MAX (size_grouped_len, 11); int progress_size = bp->width - (4 + 2 + dlbytes_size + 8 + 13); if (progress_size < 5) progress_size = 0; /* "xx% " */ if (bp->total_length > 0) { int percentage = 100.0 * size / bp->total_length; assert (percentage <= 100); if (percentage < 100) sprintf (p, "%2d%% ", percentage); else strcpy (p, "100%"); p += 4; } else APPEND_LITERAL (" "); /* The progress bar: "[====> ]" or "[++==> ]". */ if (progress_size && bp->total_length > 0) { /* Size of the initial portion. */ int insz = (double)bp->initial_length / bp->total_length * progress_size; /* Size of the downloaded portion. */ int dlsz = (double)size / bp->total_length * progress_size; char *begin; int i; assert (dlsz <= progress_size); assert (insz <= dlsz); *p++ = '['; begin = p; /* Print the initial portion of the download with '+' chars, the rest with '=' and one '>'. */ for (i = 0; i < insz; i++) *p++ = '+'; dlsz -= insz; if (dlsz > 0) { for (i = 0; i < dlsz - 1; i++) *p++ = '='; *p++ = '>'; } while (p - begin < progress_size) *p++ = ' '; *p++ = ']'; } else if (progress_size) { /* If we can't draw a real progress bar, then at least show *something* to the user. */ int ind = bp->tick % (progress_size * 2 - 6); int i, pos; /* Make the star move in two directions. */ if (ind < progress_size - 2) pos = ind + 1; else pos = progress_size - (ind - progress_size + 5); *p++ = '['; for (i = 0; i < progress_size; i++) { if (i == pos - 1) *p++ = '<'; else if (i == pos ) *p++ = '='; else if (i == pos + 1) *p++ = '>'; else *p++ = ' '; } *p++ = ']'; ++bp->tick; } /* " 234,567,890" */ sprintf (p, " %-11s", size_grouped); move_to_end (p); /* " 12.52K/s" */ if (hist->total_time > 0 && hist->total_bytes) { static const char *short_units[] = { "B/s", "K/s", "M/s", "G/s" }; int units = 0; /* Calculate the download speed using the history ring and recent data that hasn't made it to the ring yet. */ wgint dlquant = hist->total_bytes + bp->recent_bytes; double dltime = hist->total_time + (dl_total_time - bp->recent_start); double dlspeed = calc_rate (dlquant, dltime, &units); sprintf (p, " %4.*f%s", dlspeed >= 99.95 ? 0 : dlspeed >= 9.995 ? 1 : 2, dlspeed, short_units[units]); move_to_end (p); } else APPEND_LITERAL (" --.-K/s"); if (!done) { /* " eta ..m ..s"; wait for three seconds before displaying the ETA. That's because the ETA value needs a while to become reliable. */ if (bp->total_length > 0 && bp->count > 0 && dl_total_time > 3) { int eta; /* Don't change the value of ETA more than approximately once per second; doing so would cause flashing without providing any value to the user. */ if (bp->total_length != size && bp->last_eta_value != 0 && dl_total_time - bp->last_eta_time < ETA_REFRESH_INTERVAL) eta = bp->last_eta_value; else { /* Calculate ETA using the average download speed to predict the future speed. If you want to use a speed averaged over a more recent period, replace dl_total_time with hist->total_time and bp->count with hist->total_bytes. I found that doing that results in a very jerky and ultimately unreliable ETA. */ wgint bytes_remaining = bp->total_length - size; double eta_ = dl_total_time * bytes_remaining / bp->count; if (eta_ >= INT_MAX - 1) goto skip_eta; eta = (int) (eta_ + 0.5); bp->last_eta_value = eta; bp->last_eta_time = dl_total_time; } /* 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, false)); move_to_end (p); } else if (bp->total_length > 0) { skip_eta: APPEND_LITERAL (" "); } } else { /* When the download is done, print the elapsed time. */ /* Note to translators: this should not take up more room than available here. Abbreviate if necessary. */ strcpy (p, _(" in ")); move_to_end (p); /* not p+=6, think translations! */ if (dl_total_time >= 10) strcpy (p, eta_to_human_short ((int) (dl_total_time + 0.5), false)); else sprintf (p, "%ss", print_decimal (dl_total_time)); move_to_end (p); } assert (p - bp->buffer <= bp->width); while (p < bp->buffer + bp->width) *p++ = ' '; *p = '\0';}/* Print the contents of the buffer as a one-line ASCII "image" so that it can be overwritten next time. */static voiddisplay_image (char *buf){ bool old = log_set_save_context (false); logputs (LOG_VERBOSE, "\r"); logputs (LOG_VERBOSE, buf); log_set_save_context (old);}static voidbar_set_params (const char *params){ char *term = getenv ("TERM"); if (params && 0 == strcmp (params, "force")) current_impl_locked = 1; if ((opt.lfilename#ifdef HAVE_ISATTY /* The progress bar doesn't make sense if the output is not a TTY -- when logging to file, it is better to review the dots. */ || !isatty (fileno (stderr))#endif /* Normally we don't depend on terminal type because the progress bar only uses ^M to move the cursor to the beginning of line, which works even on dumb terminals. But Jamie Zawinski reports that ^M and ^H tricks don't work in Emacs shell buffers, and only make a mess. */ || (term && 0 == strcmp (term, "emacs")) ) && !current_impl_locked) { /* We're not printing to a TTY, so revert to the fallback display. #### We're recursively calling set_progress_implementation here, which is slightly kludgy. It would be nicer if we provided that function a return value indicating a failure of some sort. */ set_progress_implementation (FALLBACK_PROGRESS_IMPLEMENTATION); return; }}#ifdef SIGWINCHvoidprogress_handle_sigwinch (int sig){ received_sigwinch = 1; signal (SIGWINCH, progress_handle_sigwinch);}#endif/* Provide a short human-readable rendition of the ETA. This is like secs_to_human_time in main.c, except the output doesn't include fractions (which would look silly in by nature imprecise ETA) and takes less room. If the time is measured in hours, hours and minutes (but not seconds) are shown; if measured in days, then days and hours are shown. This ensures brevity while still displaying as much as possible. 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, 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 once per 900 msecs. */ if (secs == last) return buf; last = secs; if (secs < 100) sprintf (buf, "%ds", secs); else if (secs < 100 * 60) sprintf (buf, "%dm%s%ds", secs / 60, space, secs % 60); else if (secs < 48 * 3600) sprintf (buf, "%dh%s%dm", secs / 3600, space, (secs / 60) % 60); else if (secs < 100 * 86400) 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); return buf;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -