installer: harden the title update download against server flakiness
This commit is contained in:
parent
43ac7aa53b
commit
fc9c9fdbcb
1 changed files with 55 additions and 15 deletions
|
|
@ -33,7 +33,10 @@
|
||||||
|
|
||||||
#include <rex/ui/window_win.h>
|
#include <rex/ui/window_win.h>
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
#include <sys/wait.h>
|
||||||
#else
|
#else
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -451,10 +454,12 @@ bool DownloadToFile(const std::string& url, const std::filesystem::path& destina
|
||||||
|
|
||||||
DWORD content_length = 0;
|
DWORD content_length = 0;
|
||||||
DWORD length_size = sizeof(content_length);
|
DWORD length_size = sizeof(content_length);
|
||||||
if (WinHttpQueryHeaders(request, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
|
if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
|
||||||
WINHTTP_HEADER_NAME_BY_INDEX, &content_length, &length_size,
|
WINHTTP_HEADER_NAME_BY_INDEX, &content_length, &length_size,
|
||||||
WINHTTP_NO_HEADER_INDEX) &&
|
WINHTTP_NO_HEADER_INDEX)) {
|
||||||
content_length > 0) {
|
content_length = 0;
|
||||||
|
}
|
||||||
|
if (content_length > 0) {
|
||||||
total_bytes.store(content_length, std::memory_order_relaxed);
|
total_bytes.store(content_length, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,10 +512,19 @@ bool DownloadToFile(const std::string& url, const std::filesystem::path& destina
|
||||||
error = "Failed to write the downloaded file.";
|
error = "Failed to write the downloaded file.";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ok = received > 0;
|
if (received == 0) {
|
||||||
if (!ok) {
|
|
||||||
error = "The download was empty.";
|
error = "The download was empty.";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// A server that drops the connection mid-body looks like a normal end of
|
||||||
|
// stream to WinHTTP, so an undersized response must be caught here rather
|
||||||
|
// than surfacing later as a corrupt content package.
|
||||||
|
if (content_length > 0 && received != content_length) {
|
||||||
|
error = "The download was incomplete (received " + std::to_string(received) + " of " +
|
||||||
|
std::to_string(content_length) + " bytes).";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
if (request) {
|
if (request) {
|
||||||
|
|
@ -537,12 +551,24 @@ bool DownloadToFile(const std::string& url, const std::filesystem::path& destina
|
||||||
error = "The temporary download path is not usable.";
|
error = "The temporary download path is not usable.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::string command = "curl -fsSL --connect-timeout 30 --max-time 600 -o '" +
|
const std::string command = "curl -fsSL --connect-timeout 30 --max-time 600 --retry 3 -o '" +
|
||||||
destination_str + "' '" + url + "'";
|
destination_str + "' '" + url + "'";
|
||||||
REXLOG_INFO("Downloading Skate 3 title update via curl");
|
REXLOG_INFO("Downloading Skate 3 title update via curl");
|
||||||
const int status = std::system(command.c_str());
|
const int status = std::system(command.c_str());
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
error = "The download failed (curl exit status " + std::to_string(status) +
|
// std::system returns the raw wait status; decode it into the actual curl
|
||||||
|
// exit code (e.g. 22 = HTTP error response) for a readable message.
|
||||||
|
std::string detail;
|
||||||
|
if (status == -1) {
|
||||||
|
detail = "curl could not be launched";
|
||||||
|
} else if (WIFEXITED(status)) {
|
||||||
|
detail = "curl exit code " + std::to_string(WEXITSTATUS(status));
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
detail = "curl terminated by signal " + std::to_string(WTERMSIG(status));
|
||||||
|
} else {
|
||||||
|
detail = "curl status " + std::to_string(status);
|
||||||
|
}
|
||||||
|
error = "The download failed (" + detail +
|
||||||
"). Check your internet connection, or select the title update file manually.";
|
"). Check your internet connection, or select the title update file manually.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -628,14 +654,28 @@ bool DownloadAndStageTitleUpdate(const std::filesystem::path& game_root,
|
||||||
const auto temp_file = temp_dir / "skate3_title_update_download.tmp";
|
const auto temp_file = temp_dir / "skate3_title_update_download.tmp";
|
||||||
|
|
||||||
const std::string url = REXCVAR_GET(skate3_title_update_url);
|
const std::string url = REXCVAR_GET(skate3_title_update_url);
|
||||||
REXLOG_INFO("Downloading Skate 3 title update from {}", url);
|
|
||||||
const bool downloaded = DownloadToFile(url, temp_file, copied_bytes, total_bytes, error);
|
// The download server is known to intermittently return errors or truncated
|
||||||
bool staged = false;
|
// bodies under load, so a failed attempt (either at the HTTP level or when
|
||||||
if (downloaded) {
|
// the package fails extraction/verification) is retried from scratch before
|
||||||
staged = StageTitleUpdateFromFile(temp_file, game_root, error);
|
// giving up. The package is small, so full re-downloads are cheap.
|
||||||
|
constexpr int kAttempts = 3;
|
||||||
|
bool ok = false;
|
||||||
|
for (int attempt = 1; attempt <= kAttempts && !ok; ++attempt) {
|
||||||
|
if (attempt > 1) {
|
||||||
|
REXLOG_WARN("Skate 3 title update attempt {} failed ({}), retrying", attempt - 1, error);
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
copied_bytes.store(0, std::memory_order_relaxed);
|
||||||
|
total_bytes.store(kContainerSize, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
error.clear();
|
||||||
|
REXLOG_INFO("Downloading Skate 3 title update from {} (attempt {}/{})", url, attempt,
|
||||||
|
kAttempts);
|
||||||
|
ok = DownloadToFile(url, temp_file, copied_bytes, total_bytes, error) &&
|
||||||
|
StageTitleUpdateFromFile(temp_file, game_root, error);
|
||||||
std::filesystem::remove(temp_file, ec);
|
std::filesystem::remove(temp_file, ec);
|
||||||
return downloaded && staged;
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue