mirror of
https://github.com/pbatard/rufus.git
synced 2024-08-14 23:57:05 +00:00
[ui] use GNU wget's algorithms for rate/ETA display
This commit is contained in:
parent
4c816a519e
commit
38ba8d366c
4 changed files with 215 additions and 29 deletions
|
@ -106,6 +106,10 @@ const char* additional_copyrights =
|
|||
"https://www.gnu.org/software/fdisk\\line\n"
|
||||
"GNU General Public License (GPL) v3 or later\\line\n"
|
||||
"\\line\n"
|
||||
"Speed/ETA computation from GNU wget:\\line\n"
|
||||
"https://www.gnu.org/software/wget\\line\n"
|
||||
"GNU General Public License (GPL) v3 or later\\line\n"
|
||||
"\\line\n"
|
||||
"Additional bootloaders from KolibriOS:\\line\n"
|
||||
"https://kolibrios.org/\\line\n"
|
||||
"GNU General Public License (GPL) v2 or later\\line\n"
|
||||
|
|
10
src/rufus.rc
10
src/rufus.rc
|
@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
|||
IDD_DIALOG DIALOGEX 12, 12, 232, 326
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
EXSTYLE WS_EX_ACCEPTFILES
|
||||
CAPTION "Rufus 3.7.1574"
|
||||
CAPTION "Rufus 3.7.1575"
|
||||
FONT 9, "Segoe UI Symbol", 400, 0, 0x0
|
||||
BEGIN
|
||||
LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP
|
||||
|
@ -394,8 +394,8 @@ END
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,7,1574,0
|
||||
PRODUCTVERSION 3,7,1574,0
|
||||
FILEVERSION 3,7,1575,0
|
||||
PRODUCTVERSION 3,7,1575,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -413,13 +413,13 @@ BEGIN
|
|||
VALUE "Comments", "https://akeo.ie"
|
||||
VALUE "CompanyName", "Akeo Consulting"
|
||||
VALUE "FileDescription", "Rufus"
|
||||
VALUE "FileVersion", "3.7.1574"
|
||||
VALUE "FileVersion", "3.7.1575"
|
||||
VALUE "InternalName", "Rufus"
|
||||
VALUE "LegalCopyright", "© 2011-2019 Pete Batard (GPL v3)"
|
||||
VALUE "LegalTrademarks", "https://www.gnu.org/copyleft/gpl.html"
|
||||
VALUE "OriginalFilename", "rufus-3.7.exe"
|
||||
VALUE "ProductName", "Rufus"
|
||||
VALUE "ProductVersion", "3.7.1574"
|
||||
VALUE "ProductVersion", "3.7.1575"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
208
src/ui.c
208
src/ui.c
|
@ -44,7 +44,6 @@
|
|||
UINT_PTR UM_LANGUAGE_MENU_MAX = UM_LANGUAGE_MENU;
|
||||
HIMAGELIST hUpImageList, hDownImageList;
|
||||
extern BOOL enable_fido, use_vds;
|
||||
// TODO: Use an enum or something
|
||||
int update_progress_type = UPT_PERCENT;
|
||||
int advanced_device_section_height, advanced_format_section_height;
|
||||
// (empty) check box width, (empty) drop down width, button height (for and without dropdown match)
|
||||
|
@ -1264,25 +1263,134 @@ void UpdateProgress(int op, float percent)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The following is taken from GNU wget (progress.c)
|
||||
*/
|
||||
struct bar_progress {
|
||||
uint64_t total_length; // expected total byte count when the download finishes
|
||||
uint64_t count; // bytes downloaded so far
|
||||
uint64_t last_screen_update; // time of the last screen update, measured since the beginning of download.
|
||||
uint64_t dltime; // download time so far
|
||||
// Keep track of recent download speeds.
|
||||
struct bar_progress_hist {
|
||||
uint64_t pos;
|
||||
uint64_t times[SPEED_HISTORY_SIZE];
|
||||
uint64_t bytes[SPEED_HISTORY_SIZE];
|
||||
// The sum of times and bytes respectively, maintained for efficiency.
|
||||
uint64_t total_time;
|
||||
uint64_t total_bytes;
|
||||
} hist;
|
||||
uint64_t recent_start; // timestamp of beginning of current position.
|
||||
uint64_t recent_bytes; // bytes downloaded so far.
|
||||
BOOL stalled; // set when no data arrives for longer than STALL_START_TIME, then reset when new data arrives.
|
||||
|
||||
// The following are used to make sure that ETA information doesn't flicker.
|
||||
uint64_t last_eta_time; // time of the last update to download speed and ETA, measured since the beginning of download.
|
||||
int last_eta_value;
|
||||
};
|
||||
|
||||
// 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 twenty reads. This is
|
||||
// good because slow downloads tend to fluctuate more and a 3-second average would be too
|
||||
// erratic.
|
||||
static void bar_update(struct bar_progress* bp, uint64_t howmuch, uint64_t dltime)
|
||||
{
|
||||
struct bar_progress_hist* hist = &bp->hist;
|
||||
uint64_t 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 < SPEED_SAMPLE_MIN)
|
||||
return;
|
||||
|
||||
if (howmuch == 0) {
|
||||
// If we're not downloading anything, we might be stalling,
|
||||
// i.e. not downloading anything for an extended period of time.
|
||||
// Since 0-reads do not enter the history ring, recent_age
|
||||
// effectively measures the time since last read.
|
||||
if (recent_age >= STALL_START_TIME) {
|
||||
// If we're stalling, reset the ring contents because it's
|
||||
// stale and because it will make bar_update stop printing
|
||||
// the (bogus) current bandwidth.
|
||||
bp->stalled = TRUE;
|
||||
memset(hist, 0, sizeof(struct bar_progress_hist));
|
||||
bp->recent_bytes = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We now have a non-zero amount of to store to the speed ring.
|
||||
|
||||
// If the stall status was acquired, reset it.
|
||||
if (bp->stalled) {
|
||||
bp->stalled = FALSE;
|
||||
// "recent_age" includes the entire stalled period, which
|
||||
// could be very long. Don't update the speed ring with that
|
||||
// value because the current bandwidth would start too small.
|
||||
// Start with an arbitrary (but more reasonable) time value and
|
||||
// let it level out.
|
||||
recent_age = 1000;
|
||||
}
|
||||
|
||||
// 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 == SPEED_HISTORY_SIZE)
|
||||
hist->pos = 0;
|
||||
}
|
||||
|
||||
// This updates the progress bar as well as the data displayed on it so that we can
|
||||
// display percentage completed, rate of transfer and estimated remaining duration.
|
||||
// During init (op = OP_INIT) an optional HWND can be passed on which to look for
|
||||
// a progress bar.
|
||||
// a progress bar. Part of the code (eta, speed) comes from GNU wget.
|
||||
void UpdateProgressWithInfo(int op, int msg, uint64_t processed, uint64_t total)
|
||||
{
|
||||
static int last_update_progress_type = UPT_PERCENT;
|
||||
static struct bar_progress bp = { 0 };
|
||||
HWND hProgressDialog = (HWND)(uintptr_t)processed;
|
||||
static HWND hProgressBar = NULL;
|
||||
static uint64_t start_time = 0, last_refresh = 0;
|
||||
uint64_t rate = 0, current_time = GetTickCount64();
|
||||
static float percent = 0.0f;
|
||||
uint64_t speed = 0, current_time = GetTickCount64();
|
||||
double percent = 0.0;
|
||||
char msg_data[128];
|
||||
static BOOL bNoAltMode = FALSE;
|
||||
|
||||
if (op == OP_INIT) {
|
||||
start_time = current_time - 1;
|
||||
last_refresh = 0;
|
||||
last_update_progress_type = UPT_PERCENT;
|
||||
percent = 0.0f;
|
||||
rate = 0;
|
||||
speed = 0;
|
||||
memset(&bp, 0, sizeof(bp));
|
||||
bp.total_length = total;
|
||||
hProgressBar = NULL;
|
||||
bNoAltMode = (BOOL)msg;
|
||||
if (hProgressDialog != NULL) {
|
||||
|
@ -1296,34 +1404,88 @@ void UpdateProgressWithInfo(int op, int msg, uint64_t processed, uint64_t total)
|
|||
SendMessage(hProgressDialog, UM_PROGRESS_INIT, 0, 0);
|
||||
}
|
||||
} else if ((hProgressBar != NULL) || (op > 0)) {
|
||||
if (processed > total)
|
||||
processed = total;
|
||||
percent = (100.0f * processed) / (1.0f * total);
|
||||
// TODO: Better transfer rate computation using a weighted algorithm such as one from
|
||||
// https://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately
|
||||
rate = (current_time == start_time) ? 0 : (processed * 1000) / (current_time - start_time);
|
||||
if ((processed == total) || (current_time > last_refresh + MAX_REFRESH)) {
|
||||
if (bNoAltMode)
|
||||
update_progress_type = 0;
|
||||
if (update_progress_type == UPT_SPEED) {
|
||||
static_sprintf(msg_data, "%s/s", SizeToHumanReadable(rate, FALSE, FALSE));
|
||||
} else if (update_progress_type == UPT_TIME) {
|
||||
uint64_t seconds = (rate == 0) ? 24 * 3600 : (total - processed) / rate + 1;
|
||||
static_sprintf(msg_data, "%d:%02d:%02d", (uint32_t)(seconds / 3600), (uint16_t)((seconds % 3600) / 60), (uint16_t)(seconds % 60));
|
||||
uint64_t dl_total_time = current_time - start_time;
|
||||
uint64_t howmuch = processed - bp.count;
|
||||
bp.count = processed;
|
||||
bp.total_length = total;
|
||||
if (bp.count > bp.total_length)
|
||||
bp.total_length = bp.count;
|
||||
if (bp.total_length > 0)
|
||||
percent = (100.0f * bp.count) / (1.0f * bp.total_length);
|
||||
else
|
||||
percent = 0.0f;
|
||||
|
||||
if ((bp.hist.total_time > 999) && (bp.hist.total_bytes != 0)) {
|
||||
// Calculate the download speed using the history ring and
|
||||
// recent data that hasn't made it to the ring yet.
|
||||
uint64_t dlquant = bp.hist.total_bytes + bp.recent_bytes;
|
||||
uint64_t dltime = bp.hist.total_time + (dl_total_time - bp.recent_start);
|
||||
speed = (dltime == 0) ? 0 : (dlquant * 1000) / dltime;
|
||||
} else {
|
||||
speed = 0;
|
||||
}
|
||||
bar_update(&bp, howmuch, dl_total_time);
|
||||
|
||||
if (bNoAltMode)
|
||||
update_progress_type = UPT_PERCENT;
|
||||
switch (update_progress_type) {
|
||||
case UPT_SPEED:
|
||||
if (speed != 0)
|
||||
static_sprintf(msg_data, "%s/s", SizeToHumanReadable(speed, FALSE, FALSE));
|
||||
else
|
||||
static_sprintf(msg_data, "---");
|
||||
break;
|
||||
case UPT_ETA:
|
||||
if ((bp.total_length > 0) && (bp.count > 0) && (dl_total_time > 3000)) {
|
||||
uint32_t eta = 0;
|
||||
|
||||
// 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 != processed) && (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.
|
||||
uint64_t bytes_remaining = bp.total_length - processed;
|
||||
double d_eta = (dl_total_time / 1000.0) * (bytes_remaining * 1.0) / (bp.count * 1.0);
|
||||
if (d_eta >= INT_MAX - 1)
|
||||
goto skip_eta;
|
||||
eta = (uint32_t)(d_eta + 0.5);
|
||||
bp.last_eta_value = eta;
|
||||
bp.last_eta_time = dl_total_time;
|
||||
}
|
||||
static_sprintf(msg_data, "%d:%02d:%02d", eta / 3600, (uint16_t)((eta % 3600) / 60), (uint16_t)(eta % 60));
|
||||
} else {
|
||||
static_sprintf(msg_data, "%0.1f%%", percent);
|
||||
skip_eta:
|
||||
static_sprintf(msg_data, "-:--:--");
|
||||
}
|
||||
last_refresh = current_time;
|
||||
break;
|
||||
default:
|
||||
static_sprintf(msg_data, "%0.1f%%", percent);
|
||||
break;
|
||||
}
|
||||
if ((bp.count == bp.total_length) || (current_time > last_refresh + MAX_REFRESH)) {
|
||||
if (op < 0) {
|
||||
SendMessage(hProgressBar, PBM_SETPOS, (WPARAM)(MAX_PROGRESS * percent / 100.0f), 0);
|
||||
if (op == OP_NOOP_WITH_TASKBAR)
|
||||
SetTaskbarProgressValue((ULONGLONG)(MAX_PROGRESS * percent / 100.0f), MAX_PROGRESS);
|
||||
} else {
|
||||
UpdateProgress(op, percent);
|
||||
UpdateProgress(op, (float)percent);
|
||||
}
|
||||
if (msg >= 0)
|
||||
if ((msg >= 0) && ((current_time > bp.last_screen_update + SCREEN_REFRESH_INTERVAL) ||
|
||||
(last_update_progress_type != update_progress_type) || (bp.count == bp.total_length))) {
|
||||
PrintInfo(0, msg, msg_data);
|
||||
bp.last_screen_update = current_time;
|
||||
}
|
||||
last_refresh = current_time;
|
||||
}
|
||||
last_update_progress_type = update_progress_type;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
src/ui.h
22
src/ui.h
|
@ -47,10 +47,30 @@
|
|||
enum update_progress_type {
|
||||
UPT_PERCENT = 0,
|
||||
UPT_SPEED,
|
||||
UPT_TIME,
|
||||
UPT_ETA,
|
||||
UPT_MAX
|
||||
};
|
||||
|
||||
// Size of the download speed history ring.
|
||||
#define SPEED_HISTORY_SIZE 20
|
||||
|
||||
// The minimum time length of a history sample. By default, each sample is at least 150ms long,
|
||||
// which means that, over the course of 20 samples, "current" download speed spans at least 3s
|
||||
// into the past.
|
||||
#define SPEED_SAMPLE_MIN 150
|
||||
|
||||
// The time after which the download starts to be considered "stalled", i.e. the current
|
||||
// bandwidth is not printed and the recent download speeds are scratched.
|
||||
#define STALL_START_TIME 5000
|
||||
|
||||
// Time between screen refreshes will not be shorter than this.
|
||||
// NB: In Rufus' case, "screen" means the text overlaid on the progress bar.
|
||||
#define SCREEN_REFRESH_INTERVAL 200
|
||||
|
||||
// Don't refresh the ETA too often to avoid jerkiness in predictions.
|
||||
// This allows ETA to change approximately once per second.
|
||||
#define ETA_REFRESH_INTERVAL 990
|
||||
|
||||
extern HWND hMultiToolbar, hSaveToolbar, hHashToolbar, hAdvancedDeviceToolbar, hAdvancedFormatToolbar;
|
||||
extern HFONT hInfoFont;
|
||||
extern UINT_PTR UM_LANGUAGE_MENU_MAX;
|
||||
|
|
Loading…
Reference in a new issue