From d5162f6ce6b648344e32769a0705a43b69ed3629 Mon Sep 17 00:00:00 2001 From: Zangetsu38 Date: Mon, 27 Feb 2023 06:34:52 +0100 Subject: [PATCH] vita3k update: Add progress bar on download new build. https: Add check for null result from getaddrinfo when resolving host address. - should fix crash when internet is no available. --- vita3k/gui/src/gui.cpp | 2 +- vita3k/gui/src/vita3k_update.cpp | 17 ++++- vita3k/https/include/https/functions.h | 6 +- vita3k/https/src/https.cpp | 100 ++++++++++++++++++++----- 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/vita3k/gui/src/gui.cpp b/vita3k/gui/src/gui.cpp index 42c219926..0368da45d 100644 --- a/vita3k/gui/src/gui.cpp +++ b/vita3k/gui/src/gui.cpp @@ -67,7 +67,7 @@ void draw_info_message(GuiState &gui, EmuEnvState &emuenv) { ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", title.c_str()); ImGui::Spacing(); ImGui::Separator(); - const auto text_size = ImGui::CalcTextSize(gui.info_message.msg.c_str(), 0 , false, WINDOW_SIZE.x - (24.f * SCALE.x)); + const auto text_size = ImGui::CalcTextSize(gui.info_message.msg.c_str(), 0, false, WINDOW_SIZE.x - (24.f * SCALE.x)); const auto text_pos = ImVec2((WINDOW_SIZE.x / 2.f) - (text_size.x / 2.f), (WINDOW_SIZE.y / 2.f) - (text_size.y / 2.f) - (24 * SCALE.y)); ImGui::SetCursorPos(text_pos); ImGui::TextWrapped("%s", gui.info_message.msg.c_str()); diff --git a/vita3k/gui/src/vita3k_update.cpp b/vita3k/gui/src/vita3k_update.cpp index 7c3995653..48129305f 100644 --- a/vita3k/gui/src/vita3k_update.cpp +++ b/vita3k/gui/src/vita3k_update.cpp @@ -100,6 +100,7 @@ bool init_vita3k_update(GuiState &gui) { if (!sha.empty() && !msg.empty()) { // Replace " to \" for get back original message boost::replace_all(msg, """, "\""); + // Replace \r and \n to new line boost::replace_all(msg, "\\r\\n", "\n"); boost::replace_all(msg, "\\n", "\n"); @@ -132,6 +133,11 @@ bool init_vita3k_update(GuiState &gui) { return has_update; } +static std::atomic progress(0); +static const auto progress_callback = [](float updated_progress) { + progress = updated_progress; +}; + static void download_update(const std::string base_path) { std::thread download([base_path]() { std::string download_continuous_link = "https://github.com/Vita3K/Vita3K/releases/download/continuous"; @@ -152,7 +158,7 @@ static void download_update(const std::string base_path) { const auto vita3k_latest_path = base_path + "vita3k-latest" + archive_ext; LOG_INFO("Attempting to download and extract the latest Vita3K version {} in progress...", git_version); - if (https::download_file(download_continuous_link, vita3k_latest_path)) { + if (https::download_file(download_continuous_link, vita3k_latest_path, progress_callback)) { SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent(&event); @@ -265,6 +271,15 @@ void draw_vita3k_update(GuiState &gui, EmuEnvState &emuenv) { ImGui::SetCursorPos(ImVec2(92.f * SCALE.x, (display_size.y / 2.f) - (calc_str.y / 2.f))); ImGui::PushTextWrapPos(868.f * SCALE.x); ImGui::Text("%s", lang["downloading"].c_str()); + const float PROGRESS_BAR_WIDTH = 820.f * SCALE.x; + ImGui::SetCursorPos(ImVec2((ImGui::GetWindowWidth() / 2) - (PROGRESS_BAR_WIDTH / 2.f), ImGui::GetCursorPosY() + 30.f * emuenv.dpi_scale)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, GUI_PROGRESS_BAR); + ImGui::ProgressBar(progress / 100.f, ImVec2(PROGRESS_BAR_WIDTH, 15.f * emuenv.dpi_scale), ""); + const auto progress_str = std::to_string(uint32_t(progress)).append("%"); + ImGui::SetCursorPos(ImVec2((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize(progress_str.c_str()).x / 2.f), ImGui::GetCursorPosY() + 16.f * emuenv.dpi_scale)); + ImGui::TextColored(GUI_COLOR_TEXT, "%s", progress_str.c_str()); + ImGui::PopStyleColor(); + ImGui::PopTextWrapPos(); break; diff --git a/vita3k/https/include/https/functions.h b/vita3k/https/include/https/functions.h index b2e1e3632..443415a76 100644 --- a/vita3k/https/include/https/functions.h +++ b/vita3k/https/include/https/functions.h @@ -22,8 +22,8 @@ namespace https { -std::string get_web_response(const std::string url); -std::string get_web_regex_result(const std::string url, const std::regex regex); -bool download_file(const std::string url, std::string output_file_path); +std::string get_web_response(const std::string url, const std::string method = "GET", const std::function &progress_callback = nullptr); +std::string get_web_regex_result(const std::string url, const std::regex regex, const std::string method = "GET"); +bool download_file(const std::string url, const std::string output_file_path, const std::function &progress_callback = nullptr); } // namespace https diff --git a/vita3k/https/src/https.cpp b/vita3k/https/src/https.cpp index 2153039d3..69ebb1310 100644 --- a/vita3k/https/src/https.cpp +++ b/vita3k/https/src/https.cpp @@ -52,8 +52,9 @@ static void close_ssl(SSL *ssl) { SSL_free(ssl); } +static uint64_t file_size = 0, header_size = 0; constexpr int READ_BUFFER_SIZE = 1048; -std::string get_web_response(const std::string url) { +std::string get_web_response(const std::string url, const std::string method, const std::function &progress_callback) { #ifdef WIN32 WORD versionWanted = MAKEWORD(2, 2); WSADATA wsaData; @@ -101,8 +102,15 @@ std::string get_web_response(const std::string url) { // Get address info with using host and port auto ret = getaddrinfo(host.c_str(), "https", &hints, &result); - if (ret < 0) { - LOG_ERROR("getaddrinfo = {}", ret); + + // Check if getaddrinfo failed or unable to resolve address for host + if ((ret < 0) || !result) { + if (!result) + LOG_ERROR("Unable to resolve address for host: {}", host); + else { + LOG_ERROR("getaddrinfo error: {}", gai_strerror(ret)); + freeaddrinfo(result); + } SSL_CTX_free(ctx); close_socket(sockfd); return {}; @@ -112,6 +120,26 @@ std::string get_web_response(const std::string url) { ret = connect(sockfd, result->ai_addr, static_cast(result->ai_addrlen)); if (ret < 0) { LOG_ERROR("connect({},...) = {}, errno={}({})", sockfd, ret, errno, strerror(errno)); + freeaddrinfo(result); + SSL_CTX_free(ctx); + close_socket(sockfd); + return {}; + } + + // Check if socket is connected + int error = 0; + socklen_t errlen = sizeof(error); + ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &errlen); + if (ret < 0) { + LOG_ERROR("getsockopt({}, SOL_SOCKET, SO_ERROR, ...) failed: {}", sockfd, strerror(errno)); + freeaddrinfo(result); + SSL_CTX_free(ctx); + close_socket(sockfd); + return {}; + } + if (error != 0) { + LOG_ERROR("connect({}, ...) failed: {}", sockfd, error); + freeaddrinfo(result); SSL_CTX_free(ctx); close_socket(sockfd); return {}; @@ -125,6 +153,7 @@ std::string get_web_response(const std::string url) { char err_buf[256]; ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); LOG_ERROR("Error establishing SSL connection: {}", err_buf); + freeaddrinfo(result); SSL_CTX_free(ctx); close_ssl(ssl); close_socket(sockfd); @@ -132,7 +161,7 @@ std::string get_web_response(const std::string url) { } // Send HTTP GET request to extracted URI - std::string request = "GET " + uri + " HTTP/1.1\r\n"; + std::string request = method + " " + uri + " HTTP/1.1\r\n"; request += "Host: " + host + "\r\n"; request += "User-Agent: OpenSSL/1.1.1\r\n"; request += "Connection: close\r\n\r\n"; @@ -140,6 +169,7 @@ std::string get_web_response(const std::string url) { if (SSL_write(ssl, request.c_str(), static_cast(request.length())) <= 0) { char err_buf[256]; ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); + freeaddrinfo(result); SSL_CTX_free(ctx); close_ssl(ssl); close_socket(sockfd); @@ -149,10 +179,20 @@ std::string get_web_response(const std::string url) { std::array read_buffer{}; std::string response; + uint64_t current_size = 0; while (auto bytes_read = SSL_read(ssl, read_buffer.data(), static_cast(read_buffer.size()))) { response += std::string(read_buffer.data(), bytes_read); + current_size += bytes_read; + if (progress_callback && (file_size > 0)) { + float progress_percent = static_cast(current_size - header_size) / static_cast(file_size) * 100.0f; + progress_callback(progress_percent); + } } + if (method == "HEAD") + header_size = current_size; + + freeaddrinfo(result); SSL_CTX_free(ctx); close_ssl(ssl); close_socket(sockfd); @@ -168,11 +208,11 @@ std::string get_web_response(const std::string url) { return response; } -std::string get_web_regex_result(const std::string url, const std::regex regex) { +std::string get_web_regex_result(const std::string url, const std::regex regex, const std::string method) { std::string result; // Get the response of the web - const auto response = https::get_web_response(url); + const auto response = https::get_web_response(url, method); // Check if the response is not empty if (!response.empty()) { @@ -190,7 +230,22 @@ std::string get_web_regex_result(const std::string url, const std::regex regex) return result; } -bool download_file(const std::string url, const std::string output_file_path) { +static uint64_t get_file_size(const std::string url) { + uint64_t content_length = 0; + + // Get the file size from the header + const auto content_length_str = get_web_regex_result(url, std::regex("Content-Length: (\\d+)"), "HEAD"); + + // Check if the content length is not empty + if (!content_length_str.empty()) + content_length = std::stoll(content_length_str); + + return content_length; +} + +bool download_file(const std::string url, const std::string output_file_path, const std::function &progress_callback) { + file_size = 0; + // Get the response of the app compat db auto response = get_web_response(url); @@ -198,17 +253,28 @@ bool download_file(const std::string url, const std::string output_file_path) { if (!response.empty()) { // Check if the response is a redirection if (response.find("HTTP/1.1 302 Found") != std::string::npos) { - // Extract the redirection URL from the response - std::string location_header = "Location: "; - size_t location_index = response.find(location_header); - size_t end_of_location_index = response.find("\r\n", location_index + location_header.length()); - const auto redirected_url = response.substr(location_index + location_header.length(), end_of_location_index - location_index - location_header.length()); + // Get the redirection URL from the response header (Location) + std::smatch match; + if (std::regex_search(response, match, std::regex("Location: (https?://[^\\s]+)"))) { + const std::string redirected_url(match[1]); - // Get response of redirect url - response = get_web_response(redirected_url); - if (response.empty()) { - LOG_ERROR("Failed to get response on redirected url: {}", redirected_url); - return false; + // Get file size from the redirection URL when progress callback is not null + if (progress_callback) { + file_size = get_file_size(redirected_url); + if (file_size == 0) { + LOG_ERROR("Failed to get file size"); + return false; + } + } + + // Download the file from the redirection URL with using progress callback + response = get_web_response(redirected_url, "GET", progress_callback); + + // Check if the response is empty + if (response.empty()) { + LOG_ERROR("Failed to download file"); + return false; + } } }