From fb561da9483e47783d9cc7b526672411d4518c01 Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Mon, 4 May 2026 14:42:05 +0000 Subject: [PATCH 1/9] Create curl_tools and make get_ffmpeg_versions() use it Co-authored-by: Copilot --- CMakeLists.txt | 3 +- include/curl_tools.hh | 15 ++++++++ include/request.hh | 12 ++++++ src/cli.cc | 2 +- src/curl_tools.cc | 90 +++++++++++++++++++++++++++++++++++++++++++ src/request.cc | 53 ++----------------------- src/tui.cc | 2 +- 7 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 include/curl_tools.hh create mode 100644 src/curl_tools.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 437d18c..3e07a49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.22) project(ffmpeg-version-manager LANGUAGES C CXX - VERSION 0.1.6 + VERSION 0.1.7 ) set(CMAKE_CXX_STANDARD 17) @@ -129,6 +129,7 @@ add_executable(ffmpeg-version-manager src/ui_elements.cc src/cli.cc src/tui.cc + src/curl_tools.cc ) target_include_directories(ffmpeg-version-manager diff --git a/include/curl_tools.hh b/include/curl_tools.hh new file mode 100644 index 0000000..e71017f --- /dev/null +++ b/include/curl_tools.hh @@ -0,0 +1,15 @@ +#include +#include + +#ifndef CURL_TOOLS_H +#define CURL_TOOLS_H + +bool is_curl_cert_error(CURLcode res); +CURL* init_curl_request(std::string url); +void* set_unsecure_curl(CURL* curl); +void destroy_curl(CURL* curl); +CURLcode launch_curl_request(CURL* curl); +CURLcode launch_curl_request_result(CURL* curl, std::string* result); +int quick_curl_request(std::string url, std::string* result); + +#endif // CURL_TOOLS_H diff --git a/include/request.hh b/include/request.hh index 527f690..a1f2eb5 100644 --- a/include/request.hh +++ b/include/request.hh @@ -14,6 +14,18 @@ typedef struct FFMPEG_VERSION_S { std::string url; } FFMPEG_VERSION; +inline std::ostream& operator<<(std::ostream& ostr, const FFMPEG_VERSION& ver) +{ + ostr << ver.version << ": " << ver.url; + + return ostr; +} + +inline bool operator<(const FFMPEG_VERSION& a, const FFMPEG_VERSION& b) +{ + return a.version < b.version; +} + std::vector get_ffmpeg_versions(); std::string download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); int extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); diff --git a/src/cli.cc b/src/cli.cc index ce8b5a0..a72f6b6 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -28,7 +28,7 @@ OPTIONS default_options() void print_help() { - cout << "ffmpeg-version-manager v0.1.6" << endl; + cout << "ffmpeg-version-manager v0.1.7" << endl; cout << "Arguments:" << endl; cout << " -h/--help -> Print this screen." << endl; cout << " -u/--uninstall -> Uninstall the current ffmpeg-vm installed and the current env." << endl; diff --git a/src/curl_tools.cc b/src/curl_tools.cc new file mode 100644 index 0000000..62d7bd3 --- /dev/null +++ b/src/curl_tools.cc @@ -0,0 +1,90 @@ +#include +#include + +using namespace std; + +// Callback function to write curl response to a string +static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *response) +{ + size_t totalSize = size * nmemb; + response->append((char*)contents, totalSize); + return totalSize; +} + +bool is_curl_cert_error(CURLcode res) +{ + return res == CURLE_PEER_FAILED_VERIFICATION || res == CURLE_SSL_CACERT || res == CURLE_SSL_CACERT_BADFILE; +} + +CURL* init_curl_request(string url) +{ + CURL *curl; + + curl = curl_easy_init(); + + if (!curl) { + cerr << "Failed to initialize curl" << endl; + return NULL; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + return curl; +} + +void* set_unsecure_curl(CURL* curl) +{ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + + cout << "set_unsecure_curl: Disabling SSL Check" << endl; +} + +void destroy_curl(CURL* curl) +{ + curl_easy_cleanup(curl); +} + +CURLcode launch_curl_request(CURL* curl) +{ + return curl_easy_perform(curl); +} + +CURLcode launch_curl_request_result(CURL* curl, string* result) +{ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, result); + + return curl_easy_perform(curl); +} + +int quick_curl_request(string url, string* result) +{ + CURL *curl = init_curl_request(url); + + if(curl == NULL) return 1; + + CURLcode res = launch_curl_request_result(curl, result); + + if(is_curl_cert_error(res)) + { + result->clear(); + + set_unsecure_curl(curl); + res = launch_curl_request_result(curl, result); + } + + if(res != CURLE_OK) + { + cout << "curl_request: Error on request: " << curl_easy_strerror(res) << endl; + + destroy_curl(curl); + + return 2; + } + + destroy_curl(curl); + + return 0; +} diff --git a/src/request.cc b/src/request.cc index ac915ef..b518886 100644 --- a/src/request.cc +++ b/src/request.cc @@ -1,5 +1,6 @@ #include "request.hh" #include "ui_elements.hh" +#include "curl_tools.hh" #include #include @@ -10,8 +11,6 @@ #include #include -#include - #include #include @@ -51,6 +50,7 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::stri return totalSize; } +// https://github.com/dryark/minibrew_deploy/blob/main/curlprog.m#L31 float last_progress = 0; static int ProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { PROGRESSDATA* data = reinterpret_cast(clientp); @@ -79,55 +79,11 @@ static int ProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, vector get_ffmpeg_versions() { const char* custom_url = getenv("FFMPEGVM_URL"); - CURL *curl; - CURLcode res; string response; - - curl = curl_easy_init(); - if (!curl) { - cerr << "Failed to initialize curl" << endl; - return {}; - } - - curl_easy_setopt(curl, CURLOPT_URL, custom_url != NULL ? custom_url : FFMPEGVM_URL); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - - res = curl_easy_perform(curl); - - if (res != CURLE_OK) { - cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << endl; - - // If error is SSL CA cert issue, retry insecure - if (res == CURLE_PEER_FAILED_VERIFICATION || - res == CURLE_SSL_CACERT || - res == CURLE_SSL_CACERT_BADFILE) { - - cerr << "Retrying without SSL verification (insecure!)..." << endl; - - // Disable verification - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - - response.clear(); // reset buffer - res = curl_easy_perform(curl); - - if (res != CURLE_OK) { - cerr << "Second attempt failed: " << curl_easy_strerror(res) << endl; - curl_easy_cleanup(curl); - return {}; - } - } else { - curl_easy_cleanup(curl); - return {}; - } - } - - curl_easy_cleanup(curl); + int status = quick_curl_request(custom_url != NULL ? custom_url : FFMPEGVM_URL, &response); // Error on request - if (response.empty()) + if (status != 0 || response.empty()) return {}; nlohmann::json json = nlohmann::json::parse(response); @@ -166,7 +122,6 @@ vector get_ffmpeg_versions() string download_file(string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen) { - // TODO: Add download progress: https://github.com/dryark/minibrew_deploy/blob/main/curlprog.m#L31 CURL *curl; CURLcode res; string response; diff --git a/src/tui.cc b/src/tui.cc index cd7f9c1..7fe8c9c 100644 --- a/src/tui.cc +++ b/src/tui.cc @@ -157,7 +157,7 @@ int run_tui() container, [&] { - return window(text("ffmpeg-version-manager v0.1.6"), + return window(text("ffmpeg-version-manager v0.1.7"), hbox({ menus_component->Render() | borderEmpty | size(WIDTH, EQUAL, 15), separator(), From 7c69c2b70300c376880d72b1102bde789125ace4 Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Wed, 13 May 2026 18:12:59 +0200 Subject: [PATCH 2/9] Todo split win/linux implementation to setup env --- CMakeLists.txt | 2 ++ src/env_settings.linux.cc | 10 ++++++++++ src/env_settings.windows.cc | 10 ++++++++++ 3 files changed, 22 insertions(+) create mode 100644 src/env_settings.linux.cc create mode 100644 src/env_settings.windows.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e07a49..6c0faa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,8 @@ add_executable(ffmpeg-version-manager src/cli.cc src/tui.cc src/curl_tools.cc + src/env_settings.linux.cc + src/env_settings.windows.cc ) target_include_directories(ffmpeg-version-manager diff --git a/src/env_settings.linux.cc b/src/env_settings.linux.cc new file mode 100644 index 0000000..ad85a7b --- /dev/null +++ b/src/env_settings.linux.cc @@ -0,0 +1,10 @@ +// TODO: Use #ifdef to know if the compiled code must use windows or linux +/** +#if linux + +#include "env.hh" + +... implement ... + +#endif + */ diff --git a/src/env_settings.windows.cc b/src/env_settings.windows.cc new file mode 100644 index 0000000..212e781 --- /dev/null +++ b/src/env_settings.windows.cc @@ -0,0 +1,10 @@ +// TODO: Use #ifdef to know if the compiled code must use windows or linux +/** +#if windows + +#include "env.hh" + +... implement ... + +#endif + */ From 5f006b890f0864f97082301c95612bc672cfc354 Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Wed, 13 May 2026 20:29:43 +0200 Subject: [PATCH 3/9] Refactor environment setup for cross-platform compatibility and streamline path management --- include/environment.hh | 5 +- src/env_settings.linux.cc | 109 +++++++++++++++++++-- src/env_settings.windows.cc | 121 +++++++++++++++++++++-- src/environment.cc | 189 +----------------------------------- 4 files changed, 224 insertions(+), 200 deletions(-) diff --git a/include/environment.hh b/include/environment.hh index 75b13aa..5a994e4 100644 --- a/include/environment.hh +++ b/include/environment.hh @@ -7,4 +7,7 @@ int setup_env(std::string version); int remove_env(); std::filesystem::path get_ffmpeg_vm_dir(); -#endif // ENVIRONMENT_H \ No newline at end of file +int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home); +int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home); + +#endif // ENVIRONMENT_H diff --git a/src/env_settings.linux.cc b/src/env_settings.linux.cc index ad85a7b..f750542 100644 --- a/src/env_settings.linux.cc +++ b/src/env_settings.linux.cc @@ -1,10 +1,107 @@ -// TODO: Use #ifdef to know if the compiled code must use windows or linux -/** -#if linux +#ifndef _WIN32 -#include "env.hh" +#define HOME "HOME" -... implement ... +#include "environment.hh" + +#include +#include +#include +#include + +// If you using namespace std and windows.h it creates a confict in std::byte and windows byte +namespace fs = std::filesystem; + +int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home) +{ + fs::path bashrc_path = fs::path(home) / ".bashrc"; + std::ifstream bashrc_in(bashrc_path); + if (!bashrc_in.is_open()) { + std::cerr << "Error: Could not open " << bashrc_path << std::endl; + return 3; + } + + std::string content((std::istreambuf_iterator(bashrc_in)), std::istreambuf_iterator()); + bashrc_in.close(); + + const std::string start_marker = "# --- ffmpeg-vm start ---"; + const std::string end_marker = "# --- ffmpeg-vm end ---"; + const std::string new_section = start_marker + + "\nexport PATH=\"" + (ffmpeg_vm_dir / "bin").string() + ":$PATH\"\n" + + "export FFMPEGVM_PATH=\"" + ffmpeg_vm_dir.string() + "\"\n" + + end_marker; + + if (content.find(start_marker) != std::string::npos) { + std::cout << "ffmpeg-vm section already exists in .bashrc" << std::endl; + return 4; + } + + std::ofstream bashrc_out(bashrc_path, std::ios_base::app); + if (!bashrc_out.is_open()) { + std::cerr << "Error: Could not open " << bashrc_path << " for writing." << std::endl; + return 5; + } + bashrc_out << "\n" << new_section << "\n"; + bashrc_out.close(); + + std::ofstream ffmpeg_version(ffmpeg_vm_dir / "VERSION"); + + if (!ffmpeg_version.is_open()) { + std::cerr << "Error: Could not open " << (ffmpeg_vm_dir / "VERSION") << " for writing." << std::endl; + return 5; + } + + ffmpeg_version << version << "\n"; + ffmpeg_version.close(); + + return 0; +} + +int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home) +{ + fs::path bashrc_path = fs::path(home) / ".bashrc"; + std::ifstream bashrc_in(bashrc_path); + if (!bashrc_in.is_open()) { + std::cerr << "Error: Could not open " << bashrc_path << std::endl; + return 2; + } + + std::string content((std::istreambuf_iterator(bashrc_in)), std::istreambuf_iterator()); + bashrc_in.close(); + + const std::string start_marker = "# --- ffmpeg-vm start ---"; + const std::string end_marker = "# --- ffmpeg-vm end ---"; + + size_t start_pos = content.find(start_marker); + if (start_pos == std::string::npos) { + std::cout << "No ffmpeg-vm section found in .bashrc" << std::endl; + return 4; + } + size_t end_pos = content.find(end_marker, start_pos); + if (end_pos == std::string::npos) { + std::cout << "No end marker found after start marker in .bashrc" << std::endl; + return 5; + } + end_pos += end_marker.length(); + + // Handle newline characters to avoid extra empty lines + if (end_pos < content.length() && content[end_pos] == '\n') { + end_pos++; + } else if (start_pos > 0 && content[start_pos - 1] == '\n') { + start_pos--; + } + + content.erase(start_pos - 1, end_pos - start_pos + 1); /* -1 and +1 to include the \n */ + + std::ofstream bashrc_out(bashrc_path); + if (!bashrc_out.is_open()) { + std::cerr << "Error: Could not open " << bashrc_path << " for writing." << std::endl; + return 6; + } + bashrc_out << content; + bashrc_out.close(); + + return 0; +} #endif - */ diff --git a/src/env_settings.windows.cc b/src/env_settings.windows.cc index 212e781..692c340 100644 --- a/src/env_settings.windows.cc +++ b/src/env_settings.windows.cc @@ -1,10 +1,119 @@ -// TODO: Use #ifdef to know if the compiled code must use windows or linux -/** -#if windows +#ifdef _WIN32 -#include "env.hh" +#include "environment.hh" -... implement ... +#include +#include +#include +#include + +// If you using namespace std and windows.h it creates a confict in std::byte and windows byte +namespace fs = std::filesystem; + +#define HOME "USERPROFILE" + +#include + +int update_windows_path(const fs::path& ffmpeg_vm_dir, bool add) { + HKEY hKey; + LONG lResult; + DWORD dwType = REG_EXPAND_SZ; + DWORD dwSize = 0; + std::string currentPath; + + // Open environment key + lResult = RegOpenKeyExA(HKEY_CURRENT_USER, "Environment", 0, KEY_READ | KEY_WRITE, &hKey); + if (lResult != ERROR_SUCCESS) { + std::cerr << "Error: Could not open environment registry key" << std::endl; + return 1; + } + + // Get current PATH value + lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, NULL, &dwSize); + if (lResult == ERROR_SUCCESS) { + std::vector buffer(dwSize); + lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, (LPBYTE)buffer.data(), &dwSize); + if (lResult == ERROR_SUCCESS) { + currentPath = std::string(buffer.data(), dwSize - 1); // Remove null terminator + } + } + + // Modify PATH + std::string newPath; + std::string dir_str = (ffmpeg_vm_dir / "bin").string(); + + if (add) { + lResult = RegSetValueExA(hKey, "FFMPEGVM_PATH", 0, REG_EXPAND_SZ, + (const BYTE*)ffmpeg_vm_dir.string().c_str(), ffmpeg_vm_dir.string().length() + 1); + if(lResult == ERROR_SUCCESS) + { + SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, + (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); + } + + if (currentPath.find(dir_str) != std::string::npos) { + std::cout << "ffmpeg-vm already in PATH" << std::endl; + RegCloseKey(hKey); + return 0; + } + newPath = dir_str + ";" + currentPath; + } else { + lResult = RegDeleteValueA(hKey, "FFMPEGVM_PATH"); + if(lResult == ERROR_SUCCESS) + { + SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, + (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); + } + + size_t pos = currentPath.find(dir_str); + if (pos == std::string::npos) { + std::cout << "ffmpeg-vm not found in PATH" << std::endl; + RegCloseKey(hKey); + return 0; + } + newPath = currentPath; + newPath.erase(pos, dir_str.length() + 1); // +1 to remove the semicolon + } + + // Set new PATH value + lResult = RegSetValueExA(hKey, "Path", 0, REG_EXPAND_SZ, + (const BYTE*)newPath.c_str(), newPath.length() + 1); + if (lResult != ERROR_SUCCESS) { + std::cerr << "Error: Could not set PATH value" << std::endl; + RegCloseKey(hKey); + return 1; + } + + RegCloseKey(hKey); + + // Notify system about environment change + SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, + (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); + + return 0; +} #endif - */ + + +int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home) +{ + int status = update_windows_path(ffmpeg_vm_dir, true); + + std::ofstream ffmpeg_version(ffmpeg_vm_dir / "VERSION"); + + if (!ffmpeg_version.is_open()) { + std::cerr << "Error: Could not open " << (ffmpeg_vm_dir / "VERSION") << " for writing." << std::endl; + return 5; + } + + ffmpeg_version << version << "\n"; + ffmpeg_version.close(); + + return status; +} + +int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home) +{ + return update_windows_path(ffmpeg_vm_dir, false); +} diff --git a/src/environment.cc b/src/environment.cc index 1e254e2..065cb77 100644 --- a/src/environment.cc +++ b/src/environment.cc @@ -16,89 +16,6 @@ namespace fs = std::filesystem; #define HOME "HOME" #endif -#ifdef _WIN32 -#include - -int update_windows_path(const fs::path& ffmpeg_vm_dir, bool add) { - HKEY hKey; - LONG lResult; - DWORD dwType = REG_EXPAND_SZ; - DWORD dwSize = 0; - std::string currentPath; - - // Open environment key - lResult = RegOpenKeyExA(HKEY_CURRENT_USER, "Environment", 0, KEY_READ | KEY_WRITE, &hKey); - if (lResult != ERROR_SUCCESS) { - std::cerr << "Error: Could not open environment registry key" << std::endl; - return 1; - } - - // Get current PATH value - lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, NULL, &dwSize); - if (lResult == ERROR_SUCCESS) { - std::vector buffer(dwSize); - lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, (LPBYTE)buffer.data(), &dwSize); - if (lResult == ERROR_SUCCESS) { - currentPath = std::string(buffer.data(), dwSize - 1); // Remove null terminator - } - } - - // Modify PATH - std::string newPath; - std::string dir_str = (ffmpeg_vm_dir / "bin").string(); - - if (add) { - lResult = RegSetValueExA(hKey, "FFMPEGVM_PATH", 0, REG_EXPAND_SZ, - (const BYTE*)ffmpeg_vm_dir.string().c_str(), ffmpeg_vm_dir.string().length() + 1); - if(lResult == ERROR_SUCCESS) - { - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, - (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - } - - if (currentPath.find(dir_str) != std::string::npos) { - std::cout << "ffmpeg-vm already in PATH" << std::endl; - RegCloseKey(hKey); - return 0; - } - newPath = dir_str + ";" + currentPath; - } else { - lResult = RegDeleteValueA(hKey, "FFMPEGVM_PATH"); - if(lResult == ERROR_SUCCESS) - { - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, - (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - } - - size_t pos = currentPath.find(dir_str); - if (pos == std::string::npos) { - std::cout << "ffmpeg-vm not found in PATH" << std::endl; - RegCloseKey(hKey); - return 0; - } - newPath = currentPath; - newPath.erase(pos, dir_str.length() + 1); // +1 to remove the semicolon - } - - // Set new PATH value - lResult = RegSetValueExA(hKey, "Path", 0, REG_EXPAND_SZ, - (const BYTE*)newPath.c_str(), newPath.length() + 1); - if (lResult != ERROR_SUCCESS) { - std::cerr << "Error: Could not set PATH value" << std::endl; - RegCloseKey(hKey); - return 1; - } - - RegCloseKey(hKey); - - // Notify system about environment change - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, - (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - - return 0; -} -#endif - fs::path get_ffmpeg_vm_dir() { const char* user_ffmpeg_path = getenv("FFMPEGVM_PATH"); @@ -139,63 +56,7 @@ int setup_env(std::string version) } } -#ifdef _WIN32 - int status = update_windows_path(ffmpeg_vm_dir, true); - - std::ofstream ffmpeg_version(ffmpeg_vm_dir / "VERSION"); - - if (!ffmpeg_version.is_open()) { - std::cerr << "Error: Could not open " << (ffmpeg_vm_dir / "VERSION") << " for writing." << std::endl; - return 5; - } - - ffmpeg_version << version << "\n"; - ffmpeg_version.close(); - - return status; -#else - fs::path bashrc_path = fs::path(home) / ".bashrc"; - std::ifstream bashrc_in(bashrc_path); - if (!bashrc_in.is_open()) { - std::cerr << "Error: Could not open " << bashrc_path << std::endl; - return 3; - } - - std::string content((std::istreambuf_iterator(bashrc_in)), std::istreambuf_iterator()); - bashrc_in.close(); - - const std::string start_marker = "# --- ffmpeg-vm start ---"; - const std::string end_marker = "# --- ffmpeg-vm end ---"; - const std::string new_section = start_marker - + "\nexport PATH=\"" + (ffmpeg_vm_dir / "bin").string() + ":$PATH\"\n" - + "export FFMPEGVM_PATH=\"" + ffmpeg_vm_dir.string() + "\"\n" - + end_marker; - - if (content.find(start_marker) != std::string::npos) { - std::cout << "ffmpeg-vm section already exists in .bashrc" << std::endl; - return 4; - } - - std::ofstream bashrc_out(bashrc_path, std::ios_base::app); - if (!bashrc_out.is_open()) { - std::cerr << "Error: Could not open " << bashrc_path << " for writing." << std::endl; - return 5; - } - bashrc_out << "\n" << new_section << "\n"; - bashrc_out.close(); - - std::ofstream ffmpeg_version(ffmpeg_vm_dir / "VERSION"); - - if (!ffmpeg_version.is_open()) { - std::cerr << "Error: Could not open " << (ffmpeg_vm_dir / "VERSION") << " for writing." << std::endl; - return 5; - } - - ffmpeg_version << version << "\n"; - ffmpeg_version.close(); - - return 0; -#endif + return os_setup_env(version, ffmpeg_vm_dir, home); } int remove_env() @@ -215,51 +76,5 @@ int remove_env() } } -#ifdef _WIN32 - return update_windows_path(ffmpeg_vm_dir, false); -#else - fs::path bashrc_path = fs::path(home) / ".bashrc"; - std::ifstream bashrc_in(bashrc_path); - if (!bashrc_in.is_open()) { - std::cerr << "Error: Could not open " << bashrc_path << std::endl; - return 2; - } - - std::string content((std::istreambuf_iterator(bashrc_in)), std::istreambuf_iterator()); - bashrc_in.close(); - - const std::string start_marker = "# --- ffmpeg-vm start ---"; - const std::string end_marker = "# --- ffmpeg-vm end ---"; - - size_t start_pos = content.find(start_marker); - if (start_pos == std::string::npos) { - std::cout << "No ffmpeg-vm section found in .bashrc" << std::endl; - return 4; - } - size_t end_pos = content.find(end_marker, start_pos); - if (end_pos == std::string::npos) { - std::cout << "No end marker found after start marker in .bashrc" << std::endl; - return 5; - } - end_pos += end_marker.length(); - - // Handle newline characters to avoid extra empty lines - if (end_pos < content.length() && content[end_pos] == '\n') { - end_pos++; - } else if (start_pos > 0 && content[start_pos - 1] == '\n') { - start_pos--; - } - - content.erase(start_pos - 1, end_pos - start_pos + 1); /* -1 and +1 to include the \n */ - - std::ofstream bashrc_out(bashrc_path); - if (!bashrc_out.is_open()) { - std::cerr << "Error: Could not open " << bashrc_path << " for writing." << std::endl; - return 6; - } - bashrc_out << content; - bashrc_out.close(); - - return 0; -#endif + return os_remove_env(ffmpeg_vm_dir, home); } From 44c49c13a645b61168075f79b0932fd3a71af0ad Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Wed, 13 May 2026 19:11:30 +0000 Subject: [PATCH 4/9] Fix include and #if --- include/environment.hh | 1 + src/env_settings.linux.cc | 1 + src/env_settings.windows.cc | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/include/environment.hh b/include/environment.hh index 5a994e4..23a61e2 100644 --- a/include/environment.hh +++ b/include/environment.hh @@ -1,4 +1,5 @@ #include +#include #ifndef ENVIRONMENT_H #define ENVIRONMENT_H diff --git a/src/env_settings.linux.cc b/src/env_settings.linux.cc index f750542..170fd8c 100644 --- a/src/env_settings.linux.cc +++ b/src/env_settings.linux.cc @@ -8,6 +8,7 @@ #include #include #include +#include // If you using namespace std and windows.h it creates a confict in std::byte and windows byte namespace fs = std::filesystem; diff --git a/src/env_settings.windows.cc b/src/env_settings.windows.cc index 692c340..78e6684 100644 --- a/src/env_settings.windows.cc +++ b/src/env_settings.windows.cc @@ -6,6 +6,7 @@ #include #include #include +#include // If you using namespace std and windows.h it creates a confict in std::byte and windows byte namespace fs = std::filesystem; @@ -95,6 +96,7 @@ int update_windows_path(const fs::path& ffmpeg_vm_dir, bool add) { #endif +#ifdef _WIN32 int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home) { @@ -117,3 +119,5 @@ int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home) { return update_windows_path(ffmpeg_vm_dir, false); } + +#endif From 46f16626e9502c5eac79c6148317dccb1267802e Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Wed, 13 May 2026 22:54:53 +0200 Subject: [PATCH 5/9] Refactor Windows path management by introducing registry helper functions --- src/env_settings.windows.cc | 147 +++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/src/env_settings.windows.cc b/src/env_settings.windows.cc index 78e6684..78cf8b4 100644 --- a/src/env_settings.windows.cc +++ b/src/env_settings.windows.cc @@ -15,92 +15,117 @@ namespace fs = std::filesystem; #include -int update_windows_path(const fs::path& ffmpeg_vm_dir, bool add) { +static std::string get_registry_value(HKEY root_key, const std::string& sub_key, const std::string& value_name) +{ HKEY hKey; - LONG lResult; + std::string result; + LONG lResult = RegOpenKeyExA(root_key, sub_key.c_str(), 0, KEY_READ, &hKey); + if (lResult != ERROR_SUCCESS) return result; + DWORD dwType = REG_EXPAND_SZ; DWORD dwSize = 0; - std::string currentPath; - - // Open environment key - lResult = RegOpenKeyExA(HKEY_CURRENT_USER, "Environment", 0, KEY_READ | KEY_WRITE, &hKey); - if (lResult != ERROR_SUCCESS) { - std::cerr << "Error: Could not open environment registry key" << std::endl; - return 1; - } - - // Get current PATH value - lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, NULL, &dwSize); - if (lResult == ERROR_SUCCESS) { + lResult = RegQueryValueExA(hKey, value_name.c_str(), NULL, &dwType, NULL, &dwSize); + if (lResult == ERROR_SUCCESS && dwSize > 0) { std::vector buffer(dwSize); - lResult = RegQueryValueExA(hKey, "Path", NULL, &dwType, (LPBYTE)buffer.data(), &dwSize); + lResult = RegQueryValueExA(hKey, value_name.c_str(), NULL, &dwType, (LPBYTE)buffer.data(), &dwSize); if (lResult == ERROR_SUCCESS) { - currentPath = std::string(buffer.data(), dwSize - 1); // Remove null terminator + result = std::string(buffer.data(), dwSize - 1); } } - // Modify PATH - std::string newPath; - std::string dir_str = (ffmpeg_vm_dir / "bin").string(); + RegCloseKey(hKey); + return result; +} - if (add) { - lResult = RegSetValueExA(hKey, "FFMPEGVM_PATH", 0, REG_EXPAND_SZ, - (const BYTE*)ffmpeg_vm_dir.string().c_str(), ffmpeg_vm_dir.string().length() + 1); - if(lResult == ERROR_SUCCESS) - { - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, - (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - } +static LONG set_registry_key(HKEY root_key, const std::string& sub_key, const std::string& value_name, const std::string& value) +{ + HKEY hKey; + LONG lResult = RegOpenKeyExA(root_key, sub_key.c_str(), 0, KEY_WRITE, &hKey); + if (lResult != ERROR_SUCCESS) return lResult; - if (currentPath.find(dir_str) != std::string::npos) { - std::cout << "ffmpeg-vm already in PATH" << std::endl; - RegCloseKey(hKey); - return 0; - } - newPath = dir_str + ";" + currentPath; - } else { - lResult = RegDeleteValueA(hKey, "FFMPEGVM_PATH"); - if(lResult == ERROR_SUCCESS) - { - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, + lResult = RegSetValueExA(hKey, value_name.c_str(), 0, REG_EXPAND_SZ, + (const BYTE*)value.c_str(), value.length() + 1); + + RegCloseKey(hKey); + return lResult; +} + +static LONG delete_registry_key(HKEY root_key, const std::string& sub_key, const std::string& value_name) +{ + HKEY hKey; + LONG lResult = RegOpenKeyExA(root_key, sub_key.c_str(), 0, KEY_WRITE, &hKey); + if (lResult != ERROR_SUCCESS) return lResult; + + lResult = RegDeleteValueA(hKey, value_name.c_str()); + + RegCloseKey(hKey); + return lResult; +} + +static void notify_environment_change() +{ + SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - } +} - size_t pos = currentPath.find(dir_str); - if (pos == std::string::npos) { - std::cout << "ffmpeg-vm not found in PATH" << std::endl; - RegCloseKey(hKey); - return 0; - } - newPath = currentPath; - newPath.erase(pos, dir_str.length() + 1); // +1 to remove the semicolon +int set_windows_path(const fs::path& ffmpeg_vm_dir) +{ + std::string dir_str = (ffmpeg_vm_dir / "bin").string(); + std::string currentPath = get_registry_value(HKEY_CURRENT_USER, "Environment", "Path"); + + LONG lResult = set_registry_key(HKEY_CURRENT_USER, "Environment", "FFMPEGVM_PATH", ffmpeg_vm_dir.string()); + if (lResult == ERROR_SUCCESS) { + notify_environment_change(); + } + + if (currentPath.find(dir_str) != std::string::npos) { + std::cout << "ffmpeg-vm already in PATH" << std::endl; + return 0; } - // Set new PATH value - lResult = RegSetValueExA(hKey, "Path", 0, REG_EXPAND_SZ, - (const BYTE*)newPath.c_str(), newPath.length() + 1); + std::string newPath = dir_str + ";" + currentPath; + lResult = set_registry_key(HKEY_CURRENT_USER, "Environment", "Path", newPath); if (lResult != ERROR_SUCCESS) { std::cerr << "Error: Could not set PATH value" << std::endl; - RegCloseKey(hKey); return 1; } - RegCloseKey(hKey); - - // Notify system about environment change - SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, - (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); - + notify_environment_change(); return 0; } -#endif +int delete_windows_path(const fs::path& ffmpeg_vm_dir) +{ + std::string dir_str = (ffmpeg_vm_dir / "bin").string(); + std::string currentPath = get_registry_value(HKEY_CURRENT_USER, "Environment", "Path"); -#ifdef _WIN32 + LONG lResult = delete_registry_key(HKEY_CURRENT_USER, "Environment", "FFMPEGVM_PATH"); + if (lResult == ERROR_SUCCESS) { + notify_environment_change(); + } + + size_t pos = currentPath.find(dir_str); + if (pos == std::string::npos) { + std::cout << "ffmpeg-vm not found in PATH" << std::endl; + return 0; + } + + std::string newPath = currentPath; + newPath.erase(pos, dir_str.length() + 1); + + lResult = set_registry_key(HKEY_CURRENT_USER, "Environment", "Path", newPath); + if (lResult != ERROR_SUCCESS) { + std::cerr << "Error: Could not set PATH value" << std::endl; + return 1; + } + + notify_environment_change(); + return 0; +} int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home) { - int status = update_windows_path(ffmpeg_vm_dir, true); + int status = set_windows_path(ffmpeg_vm_dir); std::ofstream ffmpeg_version(ffmpeg_vm_dir / "VERSION"); @@ -117,7 +142,7 @@ int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home) { - return update_windows_path(ffmpeg_vm_dir, false); + return delete_windows_path(ffmpeg_vm_dir); } #endif From 68c213840cf14e4d7cf3d9d5d1f13ff7c577691e Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Fri, 29 May 2026 13:57:36 +0000 Subject: [PATCH 6/9] refractor download_file & download_file -> display_download_file --- include/curl_tools.hh | 3 +- include/request.hh | 2 +- src/cli.cc | 9 +++++- src/curl_tools.cc | 17 +++++++---- src/request.cc | 68 +++++++++++++------------------------------ src/tui.cc | 2 +- 6 files changed, 45 insertions(+), 56 deletions(-) diff --git a/include/curl_tools.hh b/include/curl_tools.hh index e71017f..1d2d088 100644 --- a/include/curl_tools.hh +++ b/include/curl_tools.hh @@ -6,7 +6,8 @@ bool is_curl_cert_error(CURLcode res); CURL* init_curl_request(std::string url); -void* set_unsecure_curl(CURL* curl); +void set_unsecure_curl(CURL* curl); +void set_progress_callback_curl(CURL* curl, void* callback_data, int (*callback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t)); void destroy_curl(CURL* curl); CURLcode launch_curl_request(CURL* curl); CURLcode launch_curl_request_result(CURL* curl, std::string* result); diff --git a/include/request.hh b/include/request.hh index a1f2eb5..6437649 100644 --- a/include/request.hh +++ b/include/request.hh @@ -27,7 +27,7 @@ inline bool operator<(const FFMPEG_VERSION& a, const FFMPEG_VERSION& b) } std::vector get_ffmpeg_versions(); -std::string download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); +std::string display_download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); int extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); #endif // REQUEST_H diff --git a/src/cli.cc b/src/cli.cc index a72f6b6..4be69a7 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -8,6 +8,7 @@ #include "environment.hh" #include "request.hh" +#include "curl_tools.hh" using namespace std; @@ -155,7 +156,13 @@ int run_cli(int argc, char *argv[]) filesystem::path downloaddir = get_ffmpeg_vm_dir(); cout << "Downloading ffmpeg " + version.version + "..." << endl; - const string fdata = download_file(version.url, NULL, NULL); + string fdata; + int status = quick_curl_request(version.url, &fdata); + + if(status != 0) + { + cout << "Error on download the file" << endl; + } cout << "Extracting files..." << endl; extract(fdata, downloaddir, NULL, NULL); diff --git a/src/curl_tools.cc b/src/curl_tools.cc index 62d7bd3..bbbb0c4 100644 --- a/src/curl_tools.cc +++ b/src/curl_tools.cc @@ -13,6 +13,8 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::stri bool is_curl_cert_error(CURLcode res) { + if(res != CURLE_OK) cout << curl_easy_strerror(res) << endl; + return res == CURLE_PEER_FAILED_VERIFICATION || res == CURLE_SSL_CACERT || res == CURLE_SSL_CACERT_BADFILE; } @@ -33,7 +35,7 @@ CURL* init_curl_request(string url) return curl; } -void* set_unsecure_curl(CURL* curl) +void set_unsecure_curl(CURL* curl) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -41,6 +43,13 @@ void* set_unsecure_curl(CURL* curl) cout << "set_unsecure_curl: Disabling SSL Check" << endl; } +void set_progress_callback_curl(CURL* curl, void* callback_data, int (*callback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t)) +{ + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, callback); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, callback_data); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // Enable download progress +} + void destroy_curl(CURL* curl) { curl_easy_cleanup(curl); @@ -75,16 +84,14 @@ int quick_curl_request(string url, string* result) res = launch_curl_request_result(curl, result); } + destroy_curl(curl); + if(res != CURLE_OK) { cout << "curl_request: Error on request: " << curl_easy_strerror(res) << endl; - destroy_curl(curl); - return 2; } - destroy_curl(curl); - return 0; } diff --git a/src/request.cc b/src/request.cc index b518886..ebce4cf 100644 --- a/src/request.cc +++ b/src/request.cc @@ -120,68 +120,42 @@ vector get_ffmpeg_versions() return list; } -string download_file(string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen) +string display_download_file(string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen) { - CURL *curl; - CURLcode res; + CURL *curl = init_curl_request(url); string response; - curl = curl_easy_init(); - if (!curl) { - cerr << "Failed to initialize curl" << endl; - return ""; - } + assert(display_slider != NULL && screen != NULL); // Use this function with a display - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + PROGRESSDATA data; - if(display_slider != NULL && screen != NULL) - { - PROGRESSDATA data; + data.display_slider = display_slider; + data.screen = screen; - data.display_slider = display_slider; - data.screen = screen; + set_progress_callback_curl(curl, &data, ProgressCallback); - curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback); - curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &data); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // Enable download progress - } - - res = curl_easy_perform(curl); - - last_progress = 0; + CURLcode res = launch_curl_request_result(curl, &response); - if (res != CURLE_OK) { - cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << endl; + if(is_curl_cert_error(res)) + { + response.clear(); - // If error is SSL CA cert issue, retry insecure - if (res == CURLE_PEER_FAILED_VERIFICATION || - res == CURLE_SSL_CACERT || - res == CURLE_SSL_CACERT_BADFILE) { + set_unsecure_curl(curl); + res = launch_curl_request_result(curl, &response); + } - cerr << "Retrying without SSL verification (insecure!)..." << endl; + // After downloading reset window to 0 + last_progress = 0; - // Disable verification - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + destroy_curl(curl); - response.clear(); // reset buffer - res = curl_easy_perform(curl); + if(res != CURLE_OK) + { + cout << "curl_request: Error on request: " << curl_easy_strerror(res) << endl; - if (res != CURLE_OK) { - cerr << "Second attempt failed: " << curl_easy_strerror(res) << endl; - curl_easy_cleanup(curl); - return ""; - } - } else { - curl_easy_cleanup(curl); - return ""; - } + return ""; } - curl_easy_cleanup(curl); return response; } diff --git a/src/tui.cc b/src/tui.cc index 7fe8c9c..67fcd20 100644 --- a/src/tui.cc +++ b/src/tui.cc @@ -107,7 +107,7 @@ int run_tui() setup_env(version.version); - const std::string fdata = download_file(version.url, &display_slider, &download_screen); + const std::string fdata = display_download_file(version.url, &display_slider, &download_screen); display_text = text(center_text("Extracting files...")); display_slider = text(generate_slider(0.0f)); From f9b8e8e9a615cbfd565b332683d7cb71dafc5acf Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Fri, 29 May 2026 14:30:17 +0000 Subject: [PATCH 7/9] Create extract_from_memory_to_folder to uncompress files & refactor extract & extract -> display_extract --- CMakeLists.txt | 1 + include/compress.hh | 13 ++++ include/request.hh | 2 +- src/cli.cc | 10 ++- src/compress.cc | 178 ++++++++++++++++++++++++++++++++++++++++++ src/request.cc | 186 +++++--------------------------------------- src/tui.cc | 2 +- 7 files changed, 222 insertions(+), 170 deletions(-) create mode 100644 include/compress.hh create mode 100644 src/compress.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c0faa3..fb3b0e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ add_executable(ffmpeg-version-manager src/curl_tools.cc src/env_settings.linux.cc src/env_settings.windows.cc + src/compress.cc ) target_include_directories(ffmpeg-version-manager diff --git a/include/compress.hh b/include/compress.hh new file mode 100644 index 0000000..92a082b --- /dev/null +++ b/include/compress.hh @@ -0,0 +1,13 @@ +#include +#include + +#ifndef COMPRESS_H +#define COMPRESS_H + +int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir, void* callback_data, void (*callback)(void*, size_t, size_t)); +int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir) +{ + return extract_from_memory_to_folder(filedata, destination_dir, NULL, NULL); +} + +#endif // COMPRESS_H \ No newline at end of file diff --git a/include/request.hh b/include/request.hh index 6437649..ffef12d 100644 --- a/include/request.hh +++ b/include/request.hh @@ -28,6 +28,6 @@ inline bool operator<(const FFMPEG_VERSION& a, const FFMPEG_VERSION& b) std::vector get_ffmpeg_versions(); std::string display_download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); -int extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); +int display_extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen); #endif // REQUEST_H diff --git a/src/cli.cc b/src/cli.cc index 4be69a7..4355e9f 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -9,6 +9,7 @@ #include "environment.hh" #include "request.hh" #include "curl_tools.hh" +#include "compress.hh" using namespace std; @@ -162,10 +163,17 @@ int run_cli(int argc, char *argv[]) if(status != 0) { cout << "Error on download the file" << endl; + return 2; } cout << "Extracting files..." << endl; - extract(fdata, downloaddir, NULL, NULL); + status = extract_from_memory_to_folder(fdata, downloaddir); + + if(status != 0) + { + cout << "Error on extracting the file" << endl; + return 3; + } #ifdef _WIN32 cout << "Done! Please open a new terminal to load the custom env for ffmpeg-vm" << endl; diff --git a/src/compress.cc b/src/compress.cc new file mode 100644 index 0000000..853413c --- /dev/null +++ b/src/compress.cc @@ -0,0 +1,178 @@ +#include "compress.hh" + +#include +#include + +#include +#include + +using namespace std; +namespace fs = std::filesystem; + +int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir, void* callback_data, void (*callback)(void*, size_t, size_t)) +{ + struct archive *archiv; + struct archive_entry *entry; + int result; + + archiv = archive_read_new(); + archive_read_support_filter_all(archiv); // Support for gzip, bzip2, xz, etc. + archive_read_support_format_all(archiv); // Support for tar, zip, 7zip, etc. + + // Load from memory + result = archive_read_open_memory(archiv, filedata.data(), filedata.size()); + + if (result != ARCHIVE_OK) + { + cerr << "Error opening archive from memory: " << archive_error_string(archiv) << endl; + archive_read_free(archiv); + return 1; + } + + // Number of chars to remove at the start of the path + size_t ffmpeg_entry_path_lenght = 0; + + size_t total_entries = 0; + size_t processed_entries = 0; + + // Read all entry to know how many files are + while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK) { + total_entries++; + archive_read_data_skip(archiv); // Saltar los datos para solo contar + } + + // Restart the read + archive_read_free(archiv); + archiv = archive_read_new(); + archive_read_support_filter_all(archiv); + archive_read_support_format_all(archiv); + result = archive_read_open_memory(archiv, filedata.data(), filedata.size()); + + if (result != ARCHIVE_OK) + { + cerr << "Error reopening archive from memory: " << archive_error_string(archiv) << endl; + archive_read_free(archiv); + return 1; + } + + // Read every entry (file/dir) inside the compressed file + while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK) + { + if (total_entries > 0 && callback != NULL) + { + processed_entries++; + + callback(callback_data, processed_entries, total_entries); + } + + fs::path entry_path = fs::path(archive_entry_pathname(entry)); + + if (ffmpeg_entry_path_lenght != 0) + { + string temp = entry_path.string(); + + temp.erase(0, ffmpeg_entry_path_lenght); + + entry_path = fs::path(temp); + } + + const fs::path full_dest_path = destination_dir / entry_path; + + /* + The first entry is the root folder + because we want all in ffmpeg-vm/... and not in ffmpeg-vm/something/..., we check this path to remove + */ + if (ffmpeg_entry_path_lenght == 0 && entry_path.string().rfind("ffmpeg") == 0) // Check if starts with ffmpeg + { + ffmpeg_entry_path_lenght = entry_path.string().length(); + continue; + } + + // Make sure that the folder exist + if (full_dest_path.has_parent_path()) + { + fs::create_directories(full_dest_path.parent_path()); + } + + // If is only a directory, create the dir + if (archive_entry_filetype(entry) == AE_IFDIR) + { + fs::create_directories(full_dest_path); + + continue; + } + +#ifndef _WIN32 + // If is only a symlink, create the symlink + if (archive_entry_filetype(entry) == AE_IFLNK) + { + const char* link_target_cstr = archive_entry_symlink(entry); + + if (link_target_cstr) { + // fs::create_symlink can fail if the link exist + std::error_code ec; + fs::remove(full_dest_path, ec); // Remove to prevent errors + fs::create_symlink(fs::path(link_target_cstr), full_dest_path); + } else { + cerr << "Warning: could not read symlink target for " << full_dest_path << endl; + } + + continue; + } +#endif + + // if it's a file, write it to disk + ofstream outfile(full_dest_path, ios::binary); + + if (!outfile) + { + cerr << "Error: Could not open file for writing: " << full_dest_path << endl; + archive_read_close(archiv); + archive_read_free(archiv); + return 2; + } + + const void *buff; // Points to the data, no need to free, is only a pointer in memory + size_t size; + la_int64_t offset; + + // Read the blocks and write it + while ((result = archive_read_data_block(archiv, &buff, &size, &offset)) == ARCHIVE_OK) + { + outfile.write(static_cast(buff), size); + } + + if (result != ARCHIVE_EOF) + { + cerr << "Error reading data block: " << archive_error_string(archiv) << endl; + archive_read_close(archiv); + archive_read_free(archiv); + return 3; + } + +#ifndef _WIN32 + // Restore permissions + if (archive_entry_filetype(entry) != AE_IFLNK) { + try { + fs::permissions(full_dest_path, static_cast(archive_entry_perm(entry)), fs::perm_options::replace); + } catch (const exception& e) { + cerr << "Warning: could not set permissions for " << full_dest_path << ". " << e.what() << endl; + } + } +#endif + } + + // Check if error on readling last header + result = archive_read_close(archiv); + + if (result != ARCHIVE_OK) + { + cerr << "Error closing archive: " << archive_error_string(archiv) << endl; + archive_read_free(archiv); + return 4; + } + + archive_read_free(archiv); + + return 0; +} diff --git a/src/request.cc b/src/request.cc index ebce4cf..a2e0b87 100644 --- a/src/request.cc +++ b/src/request.cc @@ -13,8 +13,7 @@ #include -#include -#include +#include "compress.hh" #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive @@ -52,7 +51,8 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::stri // https://github.com/dryark/minibrew_deploy/blob/main/curlprog.m#L31 float last_progress = 0; -static int ProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + +static int DownloadCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { PROGRESSDATA* data = reinterpret_cast(clientp); ftxui::Element* display_slider = data->display_slider; @@ -132,7 +132,7 @@ string display_download_file(string url, ftxui::Element* display_slider, ftxui:: data.display_slider = display_slider; data.screen = screen; - set_progress_callback_curl(curl, &data, ProgressCallback); + set_progress_callback_curl(curl, &data, DownloadCallback); CURLcode res = launch_curl_request_result(curl, &response); @@ -159,178 +159,30 @@ string display_download_file(string url, ftxui::Element* display_slider, ftxui:: return response; } -int extract(const string &filedata, const fs::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen) +static void ExtractCallback(void *callback_data, size_t processed_entries, size_t total_entries) { - struct archive *archiv; - struct archive_entry *entry; - int result; - - archiv = archive_read_new(); - archive_read_support_filter_all(archiv); // Support for gzip, bzip2, xz, etc. - archive_read_support_format_all(archiv); // Support for tar, zip, 7zip, etc. + PROGRESSDATA* data = reinterpret_cast(callback_data); - // Load from memory - result = archive_read_open_memory(archiv, filedata.data(), filedata.size()); - - if (result != ARCHIVE_OK) - { - cerr << "Error opening archive from memory: " << archive_error_string(archiv) << endl; - archive_read_free(archiv); - return 1; - } - // Number of chars to remove at the start of the path - size_t ffmpeg_entry_path_lenght = 0; - float prev_progress = 0; - size_t total_entries = 0; - size_t processed_entries = 0; + float progress = static_cast(processed_entries) / static_cast(total_entries); - // Read all entry to know how many files are - while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK) { - total_entries++; - archive_read_data_skip(archiv); // Saltar los datos para solo contar - } - - // Restart the read - archive_read_free(archiv); - archiv = archive_read_new(); - archive_read_support_filter_all(archiv); - archive_read_support_format_all(archiv); - result = archive_read_open_memory(archiv, filedata.data(), filedata.size()); - - if (result != ARCHIVE_OK) + // Reduce the amount of updates/s + if (progress - last_progress > 0.05) { - cerr << "Error reopening archive from memory: " << archive_error_string(archiv) << endl; - archive_read_free(archiv); - return 1; - } - - // Read every entry (file/dir) inside the compressed file - while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK) - { - if (total_entries > 0 && display_slider != NULL && screen != NULL) - { - processed_entries++; - - float progress = static_cast(processed_entries) / static_cast(total_entries); - - // Reduce the amount of updates/s - if (progress - prev_progress > 0.05) - { - *display_slider = ftxui::text(generate_slider(progress > 0.95 ? 1.0f : progress)); - screen->PostEvent(ftxui::Event::Custom); - } - } - - fs::path entry_path = fs::path(archive_entry_pathname(entry)); - - if (ffmpeg_entry_path_lenght != 0) - { - string temp = entry_path.string(); - - temp.erase(0, ffmpeg_entry_path_lenght); - - entry_path = fs::path(temp); - } - - const fs::path full_dest_path = destination_dir / entry_path; - - /* - The first entry is the root folder - because we want all in ffmpeg-vm/... and not in ffmpeg-vm/something/..., we check this path to remove - */ - if (ffmpeg_entry_path_lenght == 0 && entry_path.string().rfind("ffmpeg") == 0) // Check if starts with ffmpeg - { - ffmpeg_entry_path_lenght = entry_path.string().length(); - continue; - } - - // Make sure that the folder exist - if (full_dest_path.has_parent_path()) - { - fs::create_directories(full_dest_path.parent_path()); - } - - // If is only a directory, create the dir - if (archive_entry_filetype(entry) == AE_IFDIR) - { - fs::create_directories(full_dest_path); - - continue; - } - -#ifndef _WIN32 - // If is only a symlink, create the symlink - if (archive_entry_filetype(entry) == AE_IFLNK) - { - const char* link_target_cstr = archive_entry_symlink(entry); - - if (link_target_cstr) { - // fs::create_symlink can fail if the link exist - std::error_code ec; - fs::remove(full_dest_path, ec); // Remove to prevent errors - fs::create_symlink(fs::path(link_target_cstr), full_dest_path); - } else { - cerr << "Warning: could not read symlink target for " << full_dest_path << endl; - } - - continue; - } -#endif - - // if it's a file, write it to disk - ofstream outfile(full_dest_path, ios::binary); - - if (!outfile) - { - cerr << "Error: Could not open file for writing: " << full_dest_path << endl; - archive_read_close(archiv); - archive_read_free(archiv); - return 2; - } - - const void *buff; - size_t size; - la_int64_t offset; - - // Read the blocks and write it - while ((result = archive_read_data_block(archiv, &buff, &size, &offset)) == ARCHIVE_OK) - { - outfile.write(static_cast(buff), size); - } - - if (result != ARCHIVE_EOF) - { - cerr << "Error reading data block: " << archive_error_string(archiv) << endl; - archive_read_close(archiv); - archive_read_free(archiv); - return 3; - } - -#ifndef _WIN32 - // Restore permissions - if (archive_entry_filetype(entry) != AE_IFLNK) { - try { - fs::permissions(full_dest_path, static_cast(archive_entry_perm(entry)), fs::perm_options::replace); - } catch (const exception& e) { - cerr << "Warning: could not set permissions for " << full_dest_path << ". " << e.what() << endl; - } - } -#endif + *data->display_slider = ftxui::text(generate_slider(progress > 0.95 ? 1.0f : progress)); + data->screen->PostEvent(ftxui::Event::Custom); } +} - // Check if error on readling last header - result = archive_read_close(archiv); +int display_extract(const string &filedata, const fs::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen) +{ + last_progress = 0; - if (result != ARCHIVE_OK) - { - cerr << "Error closing archive: " << archive_error_string(archiv) << endl; - archive_read_free(archiv); - return 4; - } + PROGRESSDATA data; - archive_read_free(archiv); + data.display_slider = display_slider; + data.screen = screen; - return 0; + return extract_from_memory_to_folder(filedata, destination_dir, &data, ExtractCallback); } diff --git a/src/tui.cc b/src/tui.cc index 67fcd20..2bf1c2f 100644 --- a/src/tui.cc +++ b/src/tui.cc @@ -113,7 +113,7 @@ int run_tui() display_slider = text(generate_slider(0.0f)); download_screen.PostEvent(ftxui::Event::Custom); - extract(fdata, downloaddir, &display_slider, &download_screen); + display_extract(fdata, downloaddir, &display_slider, &download_screen); display_text = text(center_text("Done!")); display_slider = text(generate_slider(1.0f)); From b3ec96b747653e7a41b857eb9ed5e7fee0b9f8b3 Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Fri, 29 May 2026 14:43:09 +0000 Subject: [PATCH 8/9] Fix compile error --- include/cli.hh | 2 +- include/compress.hh | 4 ++-- include/tui.hh | 2 +- include/ui_elements.hh | 2 +- src/tui.cc | 2 +- src/ui_elements.cc | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/cli.hh b/include/cli.hh index 19c01b5..838391a 100644 --- a/include/cli.hh +++ b/include/cli.hh @@ -3,4 +3,4 @@ int run_cli(int argc, char *argv[]); -#endif // CLI_H \ No newline at end of file +#endif // CLI_H diff --git a/include/compress.hh b/include/compress.hh index 92a082b..714c5a0 100644 --- a/include/compress.hh +++ b/include/compress.hh @@ -5,9 +5,9 @@ #define COMPRESS_H int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir, void* callback_data, void (*callback)(void*, size_t, size_t)); -int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir) +inline int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir) { return extract_from_memory_to_folder(filedata, destination_dir, NULL, NULL); } -#endif // COMPRESS_H \ No newline at end of file +#endif // COMPRESS_H diff --git a/include/tui.hh b/include/tui.hh index 081dc0b..a91f7cf 100644 --- a/include/tui.hh +++ b/include/tui.hh @@ -3,4 +3,4 @@ int run_tui(); -#endif // TUI_H \ No newline at end of file +#endif // TUI_H diff --git a/include/ui_elements.hh b/include/ui_elements.hh index 450f88c..f57bc4c 100644 --- a/include/ui_elements.hh +++ b/include/ui_elements.hh @@ -10,4 +10,4 @@ void display_alert(ftxui::Element content, const std::chrono::seconds t); std::string generate_slider(float percent); std::string center_text(std::string str); -#endif // UI_ELEMENTS_H \ No newline at end of file +#endif // UI_ELEMENTS_H diff --git a/src/tui.cc b/src/tui.cc index 2bf1c2f..4f83881 100644 --- a/src/tui.cc +++ b/src/tui.cc @@ -168,4 +168,4 @@ int run_tui() screen.Loop(renderer); return 0; -} \ No newline at end of file +} diff --git a/src/ui_elements.cc b/src/ui_elements.cc index 4a21820..4fc364e 100644 --- a/src/ui_elements.cc +++ b/src/ui_elements.cc @@ -80,4 +80,4 @@ std::string center_text(std::string str) } return cent; -} \ No newline at end of file +} From f505a8c6538374e2ceeddacad1dd5f79880d9de8 Mon Sep 17 00:00:00 2001 From: adriabama06 Date: Fri, 29 May 2026 16:55:23 +0200 Subject: [PATCH 9/9] Add notes to the readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05c8567..1b7047d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # FFmpeg Version Manager -Note: I made this code in a rush, this means that the code is hard to read. - A command-line tool to manage multiple versions of FFmpeg, enabling seamless switching between different versions for development and testing. ## Features @@ -11,6 +9,11 @@ A command-line tool to manage multiple versions of FFmpeg, enabling seamless swi - Build from source for custom versions - Cross-platform support (Linux, Windows) +*Note: On Linux only works if you are using bash & ~/.bashrc, help me to improve this to more envs* + +*Note: In runtime requires $HOME for bash or %USERPROFILE% for Windows (it is usually always available; this is only to advise if you are using scripts or any other env)* + + ## Installation ### Pre-built Binaries