📄 progress.c
字号:
int pos; long times[DLSPEED_HISTORY_SIZE]; long bytes[DLSPEED_HISTORY_SIZE]; /* The sum of times and bytes respectively, maintained for efficiency. */ long total_time; long total_bytes; } hist; double recent_start; /* timestamp of beginning of current position. */ long recent_bytes; /* bytes downloaded so far. */ /* create_image() uses these to make sure that ETA information doesn't flash. */ double last_eta_time; /* time of the last update to download speed and ETA, measured since the beginning of download. */ long last_eta_value;};static void create_image PARAMS ((struct bar_progress *, double));static void display_image PARAMS ((char *));static void *bar_create (long initial, long total){ struct bar_progress *bp = xmalloc (sizeof (struct bar_progress)); memset (bp, 0, sizeof (*bp)); /* In theory, our callers should take care of this pathological case, but it can sometimes happen. */ if (initial > total) total = initial; bp->initial_length = initial; bp->total_length = total; /* - 1 because we don't want to use the last screen column. */ bp->width = screen_width - 1; /* + 1 for the terminating zero. */ bp->buffer = xmalloc (bp->width + 1); logputs (LOG_VERBOSE, "\n"); create_image (bp, 0); display_image (bp->buffer); return bp;}static void update_speed_ring PARAMS ((struct bar_progress *, long, double));static voidbar_update (void *progress, long howmuch, double dltime){ struct bar_progress *bp = progress; int force_screen_update = 0; bp->count += howmuch; if (bp->total_length > 0 && bp->count + bp->initial_length > bp->total_length) /* We could be downloading more than total_length, e.g. when the server sends an incorrect Content-Length header. In that case, adjust bp->total_length to the new reality, so that the code in create_image() that depends on total size being smaller or equal to the expected size doesn't abort. */ bp->total_length = bp->initial_length + bp->count; update_speed_ring (bp, howmuch, dltime); if (screen_width - 1 != bp->width) { bp->width = screen_width - 1; bp->buffer = xrealloc (bp->buffer, bp->width + 1); force_screen_update = 1; } if (dltime - bp->last_screen_update < 200 && !force_screen_update) /* Don't update more often than five times per second. */ return; create_image (bp, dltime); display_image (bp->buffer); bp->last_screen_update = dltime;}static voidbar_finish (void *progress, double dltime){ struct bar_progress *bp = progress; if (bp->total_length > 0 && bp->count + bp->initial_length > bp->total_length) /* See bar_update() for explanation. */ bp->total_length = bp->initial_length + bp->count; create_image (bp, dltime); display_image (bp->buffer); logputs (LOG_VERBOSE, "\n\n"); xfree (bp->buffer); xfree (bp);}/* This code attempts to maintain the notion of a "current" download speed, over the course of no less than 3s. (Shorter intervals produce very erratic results.) To do so, it samples the speed in 150ms intervals and stores the recorded samples in a FIFO history ring. The ring stores no more than 20 intervals, hence the history covers the period of at least three seconds and at most 20 reads into the past. This method should produce reasonable results for downloads ranging from very slow to very fast. The idea is that for fast downloads, we get the speed over exactly the last three seconds. For slow downloads (where a network read takes more than 150ms to complete), we get the speed over a larger time period, as large as it takes to complete thirty reads. This is good because slow downloads tend to fluctuate more and a 3-second average would be too erratic. */static voidupdate_speed_ring (struct bar_progress *bp, long howmuch, double dltime){ struct bar_progress_hist *hist = &bp->hist; double recent_age = dltime - bp->recent_start; /* Update the download count. */ bp->recent_bytes += howmuch; /* For very small time intervals, we return after having updated the "recent" download count. When its age reaches or exceeds minimum sample time, it will be recorded in the history ring. */ if (recent_age < DLSPEED_SAMPLE_MIN) return; /* 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 (sumt == hist->total_time); assert (sumb == hist->total_bytes); }#endif}#define APPEND_LITERAL(s) do { \ memcpy (p, s, sizeof (s) - 1); \ p += sizeof (s) - 1; \} while (0)#ifndef MAX# define MAX(a, b) ((a) >= (b) ? (a) : (b))#endifstatic voidcreate_image (struct bar_progress *bp, double dl_total_time){ char *p = bp->buffer; long size = bp->initial_length + bp->count; char *size_legible = legible (size); int size_legible_len = strlen (size_legible); struct bar_progress_hist *hist = &bp->hist; /* The progress bar should look like this: xx% [=======> ] nn,nnn 12.34K/s ETA 00:00 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 " 1012.56K/s" - dl rate - 11 chars " ETA xx:xx:xx" - ETA - 13 chars "=====>..." - progress bar - the rest */ int dlbytes_size = 1 + MAX (size_legible_len, 11); int progress_size = bp->width - (4 + 2 + dlbytes_size + 11 + 13); if (progress_size < 5) progress_size = 0; /* "xx% " */ if (bp->total_length > 0) { int percentage = (int)(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", legible (size)); p += strlen (p); /* " 1012.45K/s" */ if (hist->total_time && hist->total_bytes) { static 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. */ long 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, " %7.2f%s", dlspeed, short_units[units]); p += strlen (p); } else APPEND_LITERAL (" --.--K/s"); /* " ETA xx:xx:xx"; 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 && dl_total_time > 3000) { long eta; int eta_hrs, eta_min, eta_sec; /* 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 < 900) 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. */ double time_sofar = (double)dl_total_time / 1000; long bytes_remaining = bp->total_length - size; eta = (long) (time_sofar * bytes_remaining / bp->count); bp->last_eta_value = eta; bp->last_eta_time = dl_total_time; } eta_hrs = eta / 3600, eta %= 3600; eta_min = eta / 60, eta %= 60; eta_sec = eta; if (eta_hrs > 99) goto no_eta; if (eta_hrs == 0) { /* Hours not printed: pad with three spaces. */ APPEND_LITERAL (" "); sprintf (p, " ETA %02d:%02d", eta_min, eta_sec); } else { if (eta_hrs < 10) /* Hours printed with one digit: pad with one space. */ *p++ = ' '; sprintf (p, " ETA %d:%02d:%02d", eta_hrs, eta_min, eta_sec); } p += strlen (p); } else if (bp->total_length > 0) { no_eta: APPEND_LITERAL (" "); } 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){ int old = log_set_save_context (0); logputs (LOG_VERBOSE, "\r"); logputs (LOG_VERBOSE, buf); log_set_save_context (old);}static voidbar_set_params (const char *params){ int sw; 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))#else 1#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; } sw = determine_screen_width (); if (sw && sw >= MINIMUM_SCREEN_WIDTH) screen_width = sw;}#ifdef SIGWINCHRETSIGTYPEprogress_handle_sigwinch (int sig){ int sw = determine_screen_width (); if (sw && sw >= MINIMUM_SCREEN_WIDTH) screen_width = sw; signal (SIGWINCH, progress_handle_sigwinch);}#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -