Update how the ETA values are calculated to avoid negative values (#3386)

* Update calculation of ETA values to guard against incorrect Unix Epoch / time skew impacting calculation outcome.
* Use common function for ETA string calculation for upload|download operations to avoid negative values
This commit is contained in:
abraunegg 2025-07-15 06:08:41 +10:00 committed by GitHub
commit 3fff182812
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 55 deletions

View file

@ -1383,7 +1383,10 @@ class OneDriveApi {
if (fileSize >= thresholdFileSize){
// Download Progress variables
size_t expected_total_segments = 20;
ulong start_unix_time = Clock.currTime.toUnixTime();
// Time sensitive and ETA string items
SysTime currentTime = Clock.currTime();
long start_unix_time = currentTime.toUnixTime();
int h, m, s;
string etaString;
bool barInit = false;
@ -1425,10 +1428,10 @@ class OneDriveApi {
if (appConfig.getValueLong("rate_limit") > 0) {
// User configured rate limit
// How much data should be in each segment to qualify for 5%
ulong dataPerSegment = to!ulong(floor(double(dltotal)/expected_total_segments));
size_t dataPerSegment = cast(size_t)(floor(double(dltotal)/expected_total_segments));
// How much data received do we need to validate against
ulong thisSegmentData = dataPerSegment * segmentCount;
ulong nextSegmentData = dataPerSegment * (segmentCount + 1);
size_t thisSegmentData = dataPerSegment * segmentCount;
size_t nextSegmentData = dataPerSegment * (segmentCount + 1);
// Has the data that has been received in a 5% window that we need to increment the progress bar at
if ((dlnow > thisSegmentData) && (dlnow < nextSegmentData) && (previousProgressPercent != currentDLPercent) || (dlnow == dltotal)) {
@ -1440,15 +1443,16 @@ class OneDriveApi {
// Not 100% yet
// Calculate the output
segmentCount++;
auto eta = calc_eta(segmentCount, expected_total_segments, start_unix_time);
dur!"seconds"(eta).split!("hours", "minutes", "seconds")(h, m, s);
etaString = format!"| ETA %02d:%02d:%02d"( h, m, s);
// Generate ETA time output
etaString = formatETA(calc_eta(segmentCount, expected_total_segments, start_unix_time));
// Calculate percentage
string percentage = leftJustify(to!string(currentDLPercent) ~ "%", 5, ' ');
addLogEntry(downloadLogEntry ~ percentage ~ etaString, ["consoleOnly"]);
} else {
// 100% done
ulong end_unix_time = Clock.currTime.toUnixTime();
auto upload_duration = cast(int)(end_unix_time - start_unix_time);
SysTime endTime = Clock.currTime();
long end_unix_time = endTime.toUnixTime();
int upload_duration = cast(int)(end_unix_time - start_unix_time);
dur!"seconds"(upload_duration).split!("hours", "minutes", "seconds")(h, m, s);
etaString = format!"| DONE in %02d:%02d:%02d"( h, m, s);
string percentage = leftJustify(to!string(currentDLPercent) ~ "%", 5, ' ');
@ -1472,15 +1476,16 @@ class OneDriveApi {
// Not 100% yet
// Calculate the output
segmentCount++;
auto eta = calc_eta(segmentCount, expected_total_segments, start_unix_time);
dur!"seconds"(eta).split!("hours", "minutes", "seconds")(h, m, s);
etaString = format!"| ETA %02d:%02d:%02d"( h, m, s);
// Generate ETA time output
etaString = formatETA(calc_eta(segmentCount, expected_total_segments, start_unix_time));
// Calculate percentage
string percentage = leftJustify(to!string(currentDLPercent) ~ "%", 5, ' ');
addLogEntry(downloadLogEntry ~ percentage ~ etaString, ["consoleOnly"]);
} else {
// 100% done
ulong end_unix_time = Clock.currTime.toUnixTime();
auto upload_duration = cast(int)(end_unix_time - start_unix_time);
SysTime endTime = Clock.currTime();
long end_unix_time = endTime.toUnixTime();
int upload_duration = cast(int)(end_unix_time - start_unix_time);
dur!"seconds"(upload_duration).split!("hours", "minutes", "seconds")(h, m, s);
etaString = format!"| DONE in %02d:%02d:%02d"( h, m, s);
string percentage = leftJustify(to!string(currentDLPercent) ~ "%", 5, ' ');

View file

@ -9371,6 +9371,15 @@ class SyncEngine {
enum CHUNK_SIZE = 327_680L; // 320 KiB
enum MAX_FRAGMENT_BYTES = 60L * 1_048_576L; // 60 MiB = 62,914,560 bytes
// Time sensitive and ETA string items
SysTime currentTime = Clock.currTime();
long start_unix_time = currentTime.toUnixTime();
int h, m, s;
string etaString;
// Upload string template
string uploadLogEntry = "Uploading: " ~ uploadSessionData["localPath"].str ~ " ... ";
// Calculate base size using configured fragment size
baseSize = appConfig.getValueLong("file_fragment_size") * 2^^20;
@ -9391,10 +9400,6 @@ class SyncEngine {
// Estimate total number of expected fragments
size_t expected_total_fragments = cast(size_t) ceil(double(thisFileSize) / double(fragmentSize));
long start_unix_time = Clock.currTime.toUnixTime();
int h, m, s;
string etaString;
string uploadLogEntry = "Uploading: " ~ uploadSessionData["localPath"].str ~ " ... ";
// If we get a 404, create a new upload session and store it here
JSONValue newUploadSession;
@ -9405,17 +9410,9 @@ class SyncEngine {
fragmentCount++;
if (debugLogging) {addLogEntry("Fragment: " ~ to!string(fragmentCount) ~ " of " ~ to!string(expected_total_fragments), ["debug"]);}
// What ETA string do we use?
auto eta = calc_eta((fragmentCount -1), expected_total_fragments, start_unix_time);
if (eta == 0) {
// Initial calculation ...
etaString = format!"| ETA --:--:--";
} else {
// we have at least an ETA provided
dur!"seconds"(eta).split!("hours", "minutes", "seconds")(h, m, s);
etaString = format!"| ETA %02d:%02d:%02d"(h, m, s);
}
// Generate ETA time output
etaString = formatETA(calc_eta((fragmentCount -1), expected_total_fragments, start_unix_time));
// Calculate this progress output
auto ratio = cast(double)(fragmentCount - 1) / expected_total_fragments;
// Convert the ratio to a percentage and format it to two decimal places

View file

@ -1509,49 +1509,74 @@ string getUserName() {
}
// Calculate the ETA for when a 'large file' will be completed (upload & download operations)
int calc_eta(size_t counter, size_t iterations, ulong start_time) {
if (counter == 0) {
return 0; // Avoid division by zero
}
int calc_eta(size_t counter, size_t iterations, long start_time) {
if (counter == 0) {
return 0; // Avoid division by zero
}
// Get the current time as a Unix timestamp (seconds since the epoch, January 1, 1970, 00:00:00 UTC)
SysTime currentTime = Clock.currTime();
long current_time = currentTime.toUnixTime();
double ratio = cast(double) counter / iterations;
auto current_time = Clock.currTime.toUnixTime();
ulong duration = (current_time - start_time);
// 'start_time' must be less than 'current_time' otherwise ETA will have negative values
if (start_time > current_time) {
if (debugLogging) {
addLogEntry("Warning: start_time is in the future. Cannot calculate ETA.", ["debug"]);
}
return 0;
}
// Calculate duration
long duration = (current_time - start_time);
// Segments left to download
auto segments_remaining = (iterations > counter) ? (iterations - counter) : 0;
// Calculate the average time per iteration so far
double avg_time_per_iteration = cast(double) duration / counter;
// Calculate the ratio we are at
double ratio = cast(double) counter / iterations;
// Debug output for the ETA calculation
// Calculate segments left to download
auto segments_remaining = (iterations > counter) ? (iterations - counter) : 0;
// Calculate the average time per iteration so far
double avg_time_per_iteration = cast(double) duration / counter;
// Debug output for the ETA calculation
if (debugLogging) {
addLogEntry("counter: " ~ to!string(counter), ["debug"]);
addLogEntry("iterations: " ~ to!string(iterations), ["debug"]);
addLogEntry("segments_remaining: " ~ to!string(segments_remaining), ["debug"]);
addLogEntry("ratio: " ~ format("%.2f", ratio), ["debug"]);
addLogEntry("start_time: " ~ to!string(start_time), ["debug"]);
addLogEntry("current_time: " ~ to!string(current_time), ["debug"]);
addLogEntry("duration: " ~ to!string(duration), ["debug"]);
addLogEntry("counter: " ~ to!string(counter), ["debug"]);
addLogEntry("iterations: " ~ to!string(iterations), ["debug"]);
addLogEntry("segments_remaining: " ~ to!string(segments_remaining), ["debug"]);
addLogEntry("ratio: " ~ format("%.2f", ratio), ["debug"]);
addLogEntry("start_time: " ~ to!string(start_time), ["debug"]);
addLogEntry("current_time: " ~ to!string(current_time), ["debug"]);
addLogEntry("duration: " ~ to!string(duration), ["debug"]);
addLogEntry("avg_time_per_iteration: " ~ format("%.2f", avg_time_per_iteration), ["debug"]);
}
// Return the ETA or duration
if (counter != iterations) {
auto eta_sec = avg_time_per_iteration * segments_remaining;
auto eta_sec = avg_time_per_iteration * segments_remaining;
// ETA Debug
if (debugLogging) {
addLogEntry("eta_sec: " ~ to!string(eta_sec), ["debug"]);
addLogEntry("estimated_total_time: " ~ to!string(avg_time_per_iteration * iterations), ["debug"]);
addLogEntry("eta_sec: " ~ to!string(eta_sec), ["debug"]);
addLogEntry("estimated_total_time: " ~ to!string(avg_time_per_iteration * iterations), ["debug"]);
}
// Return ETA
return eta_sec > 0 ? cast(int) ceil(eta_sec) : 0;
} else {
return eta_sec > 0 ? cast(int) ceil(eta_sec) : 0;
} else {
// Return the average time per iteration for the last iteration
return cast(int) ceil(avg_time_per_iteration);
return cast(int) ceil(avg_time_per_iteration);
}
}
// Use the ETA value and return a formatted string in a consistent manner
string formatETA(int eta) {
// How do we format the ETA string. Guard against zero and negative values
if (eta <= 0) {
return "| ETA --:--:--";
}
int h, m, s;
dur!"seconds"(eta).split!("hours", "minutes", "seconds")(h, m, s);
return format!"| ETA %02d:%02d:%02d"(h, m, s);
}
// Force Exit due to failure
void forceExit() {
// Allow any logging complete before we force exit