From a1b12fe17700da32cd4c2022c18ca6eff1c32d99 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 29 Jun 2025 19:02:09 +1200 Subject: [PATCH] docs: Update 4 files --- getpkg/CMakeLists.txt | 15 +- getpkg/debug_test.txt | 1 + getpkg/src/GetbinClient.cpp | 666 ++++++++++---------------------- getpkg/src/GetbinClient.cpp.bak | 260 +++++++++++++ getpkg/src/GetbinClient.hpp | 15 +- getpkg/src/main.cpp | 48 ++- getpkg/test_debug/debug-test | 1 + getpkg/test_debug2/debug-test2 | 1 + getpkg/test_upload.txt | 1 + 9 files changed, 522 insertions(+), 486 deletions(-) create mode 100644 getpkg/debug_test.txt create mode 100644 getpkg/src/GetbinClient.cpp.bak create mode 100755 getpkg/test_debug/debug-test create mode 100755 getpkg/test_debug2/debug-test2 create mode 100644 getpkg/test_upload.txt diff --git a/getpkg/CMakeLists.txt b/getpkg/CMakeLists.txt index 79e07d5..066b418 100644 --- a/getpkg/CMakeLists.txt +++ b/getpkg/CMakeLists.txt @@ -36,13 +36,16 @@ target_include_directories(${PROJECT_NAME} PRIVATE src/common) # Find packages -find_package(OpenSSL REQUIRED) -find_package(Drogon CONFIG REQUIRED) find_package(nlohmann_json REQUIRED) +# Add module path for FindCPRStatic +list(APPEND CMAKE_MODULE_PATH "/usr/local/share/cmake/Modules") + +# Find packages +find_package(nlohmann_json REQUIRED) +find_package(CPRStatic REQUIRED) + # Link libraries target_link_libraries(${PROJECT_NAME} PRIVATE - nlohmann_json::nlohmann_json Drogon::Drogon - /usr/local/lib/libpgcommon.a /usr/local/lib/libpgport.a - lzma dl) - \ No newline at end of file + nlohmann_json::nlohmann_json + cpr::cpr_static) \ No newline at end of file diff --git a/getpkg/debug_test.txt b/getpkg/debug_test.txt new file mode 100644 index 0000000..b373e77 --- /dev/null +++ b/getpkg/debug_test.txt @@ -0,0 +1 @@ +Debug content diff --git a/getpkg/src/GetbinClient.cpp b/getpkg/src/GetbinClient.cpp index a71f29b..38fac1b 100644 --- a/getpkg/src/GetbinClient.cpp +++ b/getpkg/src/GetbinClient.cpp @@ -1,530 +1,258 @@ #include "GetbinClient.hpp" -#include -#include -#include -#include -#include -#include +#include #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include using json = nlohmann::json; -static constexpr const char* SERVER_HOST = "getpkg.xyz"; +const std::string GetbinClient::SERVER_HOST = "getpkg.xyz"; -// Initialize SSL to use only secure protocols -static class SSLInitializer { -public: - SSLInitializer() { - // Disable SSL 2.0, 3.0, TLS 1.0, and TLS 1.1 - SSL_load_error_strings(); - SSL_library_init(); - // Note: This doesn't completely silence the warning but ensures we're using secure protocols - } -} ssl_init; - -static std::string find_ca_certificates() { - // Common CA certificate locations across different Linux distributions - const std::vector ca_paths = { - "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Raspbian - "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL/CentOS - "/etc/ssl/ca-bundle.pem", // OpenSUSE - "/etc/pki/tls/cert.pem", // Fedora/RHEL alternative - "/etc/ssl/certs/ca-bundle.crt", // Some distros - "/etc/ssl/cert.pem", // Alpine Linux - "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD - "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+ - "/etc/ca-certificates/extracted/tls-ca-bundle.pem" // Arch Linux - }; - - for (const auto& path : ca_paths) { - std::ifstream file(path); - if (file.good()) { - file.close(); - return path; - } - } - - return ""; +GetbinClient::GetbinClient() { + // Initialize CPR (done automatically, but we could add global config here) } -GetbinClient::GetbinClient() {} +std::string GetbinClient::getUserAgent() const { + return "getpkg/1.0"; +} -bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) { - bool success = false; - bool done = false; - std::mutex mtx; - std::condition_variable cv; - - std::thread worker([&]() { - trantor::EventLoop loop; +bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback) { + try { + std::string url = "https://" + SERVER_HOST + "/object/" + toolName + ":" + arch; - auto client = drogon::HttpClient::newHttpClient( - "https://" + std::string(SERVER_HOST), - &loop, - false, // useOldTLS = false (disable TLS 1.0/1.1) - true // validateCert = true - ); + cpr::Session session; + session.SetUrl(cpr::Url{url}); + session.SetHeader(cpr::Header{{"User-Agent", getUserAgent()}}); + session.SetTimeout(cpr::Timeout{30000}); // 30 seconds + session.SetVerifySsl(cpr::VerifySsl{true}); - // Configure SSL certificates for HTTPS - std::string ca_path = find_ca_certificates(); - if (!ca_path.empty()) { - // Use addSSLConfigs with proper parameter names for OpenSSL - std::vector> sslConfigs; - sslConfigs.push_back({"VerifyCAFile", ca_path}); - client->addSSLConfigs(sslConfigs); - } else { - // If no CA certificates found, print warning but continue - std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; + // Add progress callback if provided + if (progressCallback) { + session.SetProgressCallback(cpr::ProgressCallback{[progressCallback](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, + cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, + intptr_t userdata) -> bool { + return progressCallback(static_cast(downloadNow), static_cast(downloadTotal)); + }}); } - client->enableCookies(); - client->setUserAgent("getpkg/1.0"); + auto response = session.Get(); - std::string object_path = "/object/" + toolName + ":" + arch; - - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath(object_path); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - std::lock_guard lock(mtx); - if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) { - std::ofstream ofs(outPath, std::ios::binary); - if (ofs) { - const auto& body = response->getBody(); - ofs.write(body.data(), body.size()); - success = ofs.good(); - } - } else { - std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl; + if (response.status_code == 200) { + std::ofstream ofs(outPath, std::ios::binary); + if (ofs) { + ofs.write(response.text.data(), response.text.size()); + return ofs.good(); } - done = true; - cv.notify_one(); - loop.quit(); - }, 30.0); // 30 second timeout + } else if (response.status_code == 404) { + // Not found - this is expected for arch fallback + return false; + } else { + std::cerr << "[GetbinClient::download] HTTP " << response.status_code << ": " << response.error.message << std::endl; + } - loop.loop(); - }); - - // Wait for completion - { - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return done; }); - } - - worker.join(); - return success; -} - -bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) { - // Read file first - std::ifstream ifs(archivePath, std::ios::binary); - if (!ifs) { - std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl; + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::download] Exception: " << e.what() << std::endl; return false; } - std::string file_content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - - // Compose metadata - json metadata = { {"labeltags", json::array()} }; - std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1); - size_t dot = filename.find('.'); - std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename; - metadata["labeltags"].push_back(labeltag); - - bool success = false; - bool done = false; - std::mutex mtx; - std::condition_variable cv; - - std::thread worker([&]() { - trantor::EventLoop loop; +} + +bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, + const std::string& token, ProgressCallback progressCallback) { + try { + std::string url = "https://" + SERVER_HOST + "/upload"; - auto client = drogon::HttpClient::newHttpClient( - "https://" + std::string(SERVER_HOST), - &loop, - false, // useOldTLS = false (disable TLS 1.0/1.1) - true // validateCert = true - ); + cpr::Session session; + session.SetUrl(cpr::Url{url}); + session.SetHeader(cpr::Header{ + {"User-Agent", getUserAgent()}, + {"Authorization", "Bearer " + token} + }); + session.SetTimeout(cpr::Timeout{300000}); // 5 minutes for uploads + session.SetVerifySsl(cpr::VerifySsl{true}); - // Configure SSL certificates - std::string ca_path = find_ca_certificates(); - std::vector> sslConfigs; - if (!ca_path.empty()) { - sslConfigs.push_back({"VerifyCAFile", ca_path}); - } - // Configure SSL for secure connections - client->addSSLConfigs(sslConfigs); - if (ca_path.empty()) { - std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; + // Extract tool name and arch from archive path for labeltags + // Archive path format: /path/to/tool-name:arch.tgz or similar + std::string archiveName = std::filesystem::path(archivePath).filename().string(); + std::string toolNameArch = archiveName; + if (toolNameArch.ends_with(".tgz")) { + toolNameArch = toolNameArch.substr(0, toolNameArch.length() - 4); } - client->enableCookies(); - client->setUserAgent("getpkg/1.0"); + // Create metadata JSON with labeltags + json metadata; + metadata["labeltags"] = json::array({toolNameArch}); - // Create upload file from memory content - // First save content to a temporary file since UploadFile expects a file path - std::string temp_file = "/tmp/getpkg_upload_" + std::to_string(std::time(nullptr)) + ".tgz"; - std::ofstream temp_ofs(temp_file, std::ios::binary); - if (!temp_ofs) { - std::cerr << "[GetbinClient::upload] Failed to create temporary file: " << temp_file << std::endl; - success = false; - done = true; - cv.notify_one(); - loop.quit(); - return; + // Set up multipart form with file and metadata + session.SetMultipart(cpr::Multipart{ + cpr::Part{"file", cpr::File{archivePath}}, + cpr::Part{"metadata", metadata.dump(), "application/json"} + }); + + // Add progress callback if provided + if (progressCallback) { + session.SetProgressCallback(cpr::ProgressCallback{[progressCallback](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, + cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, + intptr_t userdata) -> bool { + return progressCallback(static_cast(uploadNow), static_cast(uploadTotal)); + }}); } - temp_ofs.write(file_content.data(), file_content.size()); - temp_ofs.close(); - // Create upload request with file - drogon::UploadFile upload_file(temp_file); + auto response = session.Put(); - auto req = drogon::HttpRequest::newFileUploadRequest({upload_file}); - req->setMethod(drogon::Put); - req->setPath("/upload"); - req->addHeader("Authorization", "Bearer " + token); - - // Add metadata as form parameter - req->setParameter("metadata", metadata.dump()); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - std::lock_guard lock(mtx); - if (result == drogon::ReqResult::Ok && response) { - int status_code = static_cast(response->getStatusCode()); - std::string response_body(response->getBody()); - - if (status_code == 200 || status_code == 201) { - try { - auto resp_json = json::parse(response_body); - if (resp_json.contains("url")) outUrl = resp_json["url"].get(); - if (resp_json.contains("hash")) outHash = resp_json["hash"].get(); - success = true; - } catch (const std::exception& e) { - std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl; - std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; - } - } else { - std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl; - std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; + if (response.status_code == 200) { + try { + auto resp_json = json::parse(response.text); + if (resp_json.contains("hash") && resp_json.contains("result") && resp_json["result"] == "success") { + outUrl = "https://" + SERVER_HOST + "/object/" + resp_json["hash"].get(); + outHash = resp_json["hash"].get(); + return true; } - } else { - std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl; + } catch (const json::exception& e) { + // Try to extract from plain text response + outUrl = ""; + outHash = response.text; + // Remove trailing newline if present + if (!outHash.empty() && outHash.back() == '\n') { + outHash.pop_back(); + } + return !outHash.empty(); } - done = true; - cv.notify_one(); - loop.quit(); - }, 60.0); // 60 second timeout + } else { + std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << ": " << response.error.message << std::endl; + if (!response.text.empty()) { + std::cerr << "[GetbinClient::upload] Response: " << response.text << std::endl; + } + } - loop.loop(); - - // Clean up temporary file - std::remove(temp_file.c_str()); - }); - - // Wait for completion - { - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return done; }); + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::upload] Exception: " << e.what() << std::endl; + return false; } - - worker.join(); - return success; } bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { - bool success = false; - bool done = false; - std::mutex mtx; - std::condition_variable cv; - - std::thread worker([&]() { - trantor::EventLoop loop; + try { + std::string url = "https://" + SERVER_HOST + "/hash/" + toolName + ":" + arch; - auto client = drogon::HttpClient::newHttpClient( - "https://" + std::string(SERVER_HOST), - &loop, - false, // useOldTLS = false (disable TLS 1.0/1.1) - true // validateCert = true - ); + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{{"User-Agent", getUserAgent()}}, + cpr::Timeout{10000}, // 10 seconds + cpr::VerifySsl{true}); - // Configure SSL certificates - std::string ca_path = find_ca_certificates(); - std::vector> sslConfigs; - if (!ca_path.empty()) { - sslConfigs.push_back({"VerifyCAFile", ca_path}); - } - // Configure SSL for secure connections - client->addSSLConfigs(sslConfigs); - - if (ca_path.empty()) { - std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; - } - - client->enableCookies(); - client->setUserAgent("getpkg/1.0"); - - std::string hash_path = "/hash/" + toolName + ":" + arch; - - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath(hash_path); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - std::lock_guard lock(mtx); - if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) { - std::string response_body(response->getBody()); - - // Try to parse hash from response body - try { - // Try JSON first - auto resp_json = json::parse(response_body); - if (resp_json.contains("hash")) { - outHash = resp_json["hash"].get(); - success = true; - } - } catch (...) { - // Not JSON, treat as plain text - outHash = response_body; - // Remove trailing newline if present - if (!outHash.empty() && outHash.back() == '\n') { - outHash.pop_back(); - } - success = !outHash.empty(); + if (response.status_code == 200) { + try { + // Try JSON first + auto resp_json = json::parse(response.text); + if (resp_json.contains("hash")) { + outHash = resp_json["hash"].get(); + return true; } + } catch (const json::exception&) { + // Not JSON, treat as plain text + outHash = response.text; + // Remove trailing newline if present + if (!outHash.empty() && outHash.back() == '\n') { + outHash.pop_back(); + } + return !outHash.empty(); } - done = true; - cv.notify_one(); - loop.quit(); - }, 10.0); // 10 second timeout + } else if (response.status_code == 404) { + // Not found - this is expected for non-existent tools/archs + return false; + } else { + std::cerr << "[GetbinClient::getHash] HTTP " << response.status_code << ": " << response.error.message << std::endl; + } - loop.loop(); - }); - - // Wait for completion - { - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return done; }); + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::getHash] Exception: " << e.what() << std::endl; + return false; } - - worker.join(); - return success; } bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) { - bool success = false; - bool done = false; - std::mutex mtx; - std::condition_variable cv; - - std::thread worker([&]() { - trantor::EventLoop loop; + try { + std::string url = "https://" + SERVER_HOST + "/deleteobject?hash=" + hash; - auto client = drogon::HttpClient::newHttpClient( - "https://" + std::string(SERVER_HOST), - &loop, - false, // useOldTLS = false (disable TLS 1.0/1.1) - true // validateCert = true - ); + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{ + {"User-Agent", getUserAgent()}, + {"Authorization", "Bearer " + token} + }, + cpr::Timeout{30000}, // 30 seconds + cpr::VerifySsl{true}); - // Configure SSL certificates - std::string ca_path = find_ca_certificates(); - std::vector> sslConfigs; - if (!ca_path.empty()) { - sslConfigs.push_back({"VerifyCAFile", ca_path}); - } - // Configure SSL for secure connections - client->addSSLConfigs(sslConfigs); - - if (ca_path.empty()) { - std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl; - } - - client->enableCookies(); - client->setUserAgent("getpkg/1.0"); - - std::string delete_path = "/deleteobject?hash=" + hash; - - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath(delete_path); - req->addHeader("Authorization", "Bearer " + token); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - std::lock_guard lock(mtx); - if (result == drogon::ReqResult::Ok && response) { - int status_code = static_cast(response->getStatusCode()); - std::string response_body(response->getBody()); - - if (status_code == 200) { - // Check if the response indicates success - try { - auto resp_json = json::parse(response_body); - if (resp_json.contains("result") && resp_json["result"] == "success") { - success = true; - } - } catch (...) { - // If not JSON, assume success if 200 OK - success = true; - } - } else { - std::cerr << "[GetbinClient::deleteObject] HTTP error: status code " << status_code << std::endl; - std::cerr << "[GetbinClient::deleteObject] Response body: " << response_body << std::endl; - } - } else { - std::cerr << "[GetbinClient::deleteObject] HTTP request failed." << std::endl; + if (response.status_code == 200) { + return true; + } else { + std::cerr << "[GetbinClient::deleteObject] HTTP " << response.status_code << ": " << response.error.message << std::endl; + if (!response.text.empty()) { + std::cerr << "[GetbinClient::deleteObject] Response: " << response.text << std::endl; } - done = true; - cv.notify_one(); - loop.quit(); - }, 10.0); // 10 second timeout + } - loop.loop(); - }); - - // Wait for completion - { - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return done; }); + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::deleteObject] Exception: " << e.what() << std::endl; + return false; } - - worker.join(); - return success; } + bool GetbinClient::listPackages(std::vector& outPackages) { - outPackages.clear(); - - // Set up SSL configuration - std::string ca_path = find_ca_certificates(); - - bool success = false; - bool done = false; - std::mutex mtx; - std::condition_variable cv; - - std::thread worker([&]() { - trantor::EventLoop loop; + try { + std::string url = "https://" + SERVER_HOST + "/packages"; - auto client = drogon::HttpClient::newHttpClient( - "https://" + std::string(SERVER_HOST), - &loop, - false, // useOldTLS = false (disable TLS 1.0/1.1) - true // validateCert = true - ); - std::vector> sslConfigs; - if (!ca_path.empty()) { - sslConfigs.push_back({"VerifyCAFile", ca_path}); - } - // Configure SSL for secure connections - client->addSSLConfigs(sslConfigs); + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{{"User-Agent", getUserAgent()}}, + cpr::Timeout{30000}, // 30 seconds + cpr::VerifySsl{true}); - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath("/dir"); - - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { - if (result == drogon::ReqResult::Ok) { - int status_code = response->getStatusCode(); - std::string response_body = std::string(response->getBody()); - - if (status_code == 200) { - try { - json json_response = json::parse(response_body); - - if (json_response.contains("entries") && json_response["entries"].is_array()) { - for (const auto& entry : json_response["entries"]) { - if (entry.contains("labeltags") && entry["labeltags"].is_array()) { - for (const auto& labeltag : entry["labeltags"]) { - if (labeltag.is_string()) { - std::string name = labeltag.get(); - // Extract tool name (remove architecture suffix if present) - size_t colon_pos = name.find(":"); - if (colon_pos != std::string::npos) { - name = name.substr(0, colon_pos); - } - - // Skip empty names - if (name.empty()) continue; - - // Add to list if not already present - if (std::find(outPackages.begin(), outPackages.end(), name) == outPackages.end()) { - outPackages.push_back(name); - } - } - } - } - } - success = true; + if (response.status_code == 200) { + try { + auto resp_json = json::parse(response.text); + if (resp_json.is_array()) { + outPackages.clear(); + for (const auto& item : resp_json) { + if (item.is_string()) { + outPackages.push_back(item.get()); } - } catch (const std::exception& e) { - std::cerr << "[GetbinClient::listPackages] JSON parse error: " << e.what() << std::endl; } - } else { - std::cerr << "[GetbinClient::listPackages] HTTP error: status code " << status_code << std::endl; + return true; + } else if (resp_json.contains("packages") && resp_json["packages"].is_array()) { + outPackages.clear(); + for (const auto& item : resp_json["packages"]) { + if (item.is_string()) { + outPackages.push_back(item.get()); + } + } + return true; } - } else { - std::cerr << "[GetbinClient::listPackages] HTTP request failed." << std::endl; - } - done = true; - cv.notify_one(); - loop.quit(); - }, 10.0); - - loop.loop(); - }); - - // Wait for completion - { - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return done; }); - } - - worker.join(); - - // Filter out duplicates where we have both toolname and toolname-noarch - // Keep the base name and remove the -noarch variant - std::vector filteredPackages; - std::set baseNames; - - // First pass: collect all base names (without -noarch) - for (const auto& pkg : outPackages) { - const std::string suffix = "-noarch"; - if (pkg.length() < suffix.length() || pkg.substr(pkg.length() - suffix.length()) != suffix) { - baseNames.insert(pkg); - } - } - - // Second pass: add packages, skipping -noarch variants if base exists - for (const auto& pkg : outPackages) { - const std::string suffix = "-noarch"; - if (pkg.length() >= suffix.length() && pkg.substr(pkg.length() - suffix.length()) == suffix) { - std::string baseName = pkg.substr(0, pkg.length() - suffix.length()); - if (baseNames.find(baseName) == baseNames.end()) { - filteredPackages.push_back(pkg); // Keep -noarch only if no base version + } catch (const json::exception&) { + // Try to parse as newline-separated list + outPackages.clear(); + std::istringstream stream(response.text); + std::string line; + while (std::getline(stream, line)) { + if (!line.empty()) { + outPackages.push_back(line); + } + } + return !outPackages.empty(); } } else { - filteredPackages.push_back(pkg); // Always keep base versions + std::cerr << "[GetbinClient::listPackages] HTTP " << response.status_code << ": " << response.error.message << std::endl; } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::listPackages] Exception: " << e.what() << std::endl; + return false; } - - outPackages = std::move(filteredPackages); - - // Sort the packages for better display - std::sort(outPackages.begin(), outPackages.end()); - - return success; -} +} \ No newline at end of file diff --git a/getpkg/src/GetbinClient.cpp.bak b/getpkg/src/GetbinClient.cpp.bak new file mode 100644 index 0000000..c5992c2 --- /dev/null +++ b/getpkg/src/GetbinClient.cpp.bak @@ -0,0 +1,260 @@ +#include "GetbinClient.hpp" +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +const std::string GetbinClient::SERVER_HOST = "getpkg.xyz"; + +GetbinClient::GetbinClient() { + // Initialize CPR (done automatically, but we could add global config here) +} + +std::string GetbinClient::getUserAgent() const { + return "getpkg/1.0"; +} + +bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback) { + try { + std::string url = "https://" + SERVER_HOST + "/object/" + toolName + ":" + arch; + + cpr::Session session; + session.SetUrl(cpr::Url{url}); + session.SetHeader(cpr::Header{{"User-Agent", getUserAgent()}}); + session.SetTimeout(cpr::Timeout{30000}); // 30 seconds + session.SetVerifySsl(cpr::VerifySsl{true}); + + // Add progress callback if provided + if (progressCallback) { + session.SetProgressCallback(cpr::ProgressCallback{[progressCallback](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, + cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, + intptr_t userdata) -> bool { + return progressCallback(static_cast(downloadNow), static_cast(downloadTotal)); + }}); + } + + auto response = session.Get(); + + if (response.status_code == 200) { + std::ofstream ofs(outPath, std::ios::binary); + if (ofs) { + ofs.write(response.text.data(), response.text.size()); + return ofs.good(); + } + } else if (response.status_code == 404) { + // Not found - this is expected for arch fallback + return false; + } else { + std::cerr << "[GetbinClient::download] HTTP " << response.status_code << ": " << response.error.message << std::endl; + } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::download] Exception: " << e.what() << std::endl; + return false; + } +} + +bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, + const std::string& token, ProgressCallback progressCallback) { + try { + std::string url = "https://" + SERVER_HOST + "/upload"; + + cpr::Session session; + session.SetUrl(cpr::Url{url}); + session.SetHeader(cpr::Header{ + {"User-Agent", getUserAgent()}, + {"Authorization", "Bearer " + token} + }); + session.SetTimeout(cpr::Timeout{300000}); // 5 minutes for uploads + session.SetVerifySsl(cpr::VerifySsl{true}); + + // Read file for upload + std::ifstream file(archivePath, std::ios::binary); + if (!file) { + std::cerr << "[GetbinClient::upload] Cannot open file: " << archivePath << std::endl; + return false; + } + + // Get file size + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // Read file content + std::string fileContent(fileSize, '\0'); + file.read(&fileContent[0], fileSize); + file.close(); + + // Set up multipart form + session.SetMultipart(cpr::Multipart{ + cpr::Part{"file", fileContent, "application/gzip"} + }); + + // Add progress callback if provided + if (progressCallback) { + session.SetProgressCallback(cpr::ProgressCallback{[progressCallback](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, + cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, + intptr_t userdata) -> bool { + return progressCallback(static_cast(uploadNow), static_cast(uploadTotal)); + }}); + } + + auto response = session.Post(); + + if (response.status_code == 200) { + try { + auto resp_json = json::parse(response.text); + if (resp_json.contains("url") && resp_json.contains("hash")) { + outUrl = resp_json["url"].get(); + outHash = resp_json["hash"].get(); + return true; + } + } catch (const json::exception& e) { + // Try to extract from plain text response + outUrl = ""; + outHash = response.text; + // Remove trailing newline if present + if (!outHash.empty() && outHash.back() == '\n') { + outHash.pop_back(); + } + return !outHash.empty(); + } + } else { + std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << ": " << response.error.message << std::endl; + if (!response.text.empty()) { + std::cerr << "[GetbinClient::upload] Response: " << response.text << std::endl; + } + } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::upload] Exception: " << e.what() << std::endl; + return false; + } +} + +bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { + try { + std::string url = "https://" + SERVER_HOST + "/hash/" + toolName + ":" + arch; + + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{{"User-Agent", getUserAgent()}}, + cpr::Timeout{10000}, // 10 seconds + cpr::VerifySsl{true}); + + if (response.status_code == 200) { + try { + // Try JSON first + auto resp_json = json::parse(response.text); + if (resp_json.contains("hash")) { + outHash = resp_json["hash"].get(); + return true; + } + } catch (const json::exception&) { + // Not JSON, treat as plain text + outHash = response.text; + // Remove trailing newline if present + if (!outHash.empty() && outHash.back() == '\n') { + outHash.pop_back(); + } + return !outHash.empty(); + } + } else if (response.status_code == 404) { + // Not found - this is expected for non-existent tools/archs + return false; + } else { + std::cerr << "[GetbinClient::getHash] HTTP " << response.status_code << ": " << response.error.message << std::endl; + } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::getHash] Exception: " << e.what() << std::endl; + return false; + } +} + +bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) { + try { + std::string url = "https://" + SERVER_HOST + "/deleteobject?hash=" + hash; + + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{ + {"User-Agent", getUserAgent()}, + {"Authorization", "Bearer " + token} + }, + cpr::Timeout{30000}, // 30 seconds + cpr::VerifySsl{true}); + + if (response.status_code == 200) { + return true; + } else { + std::cerr << "[GetbinClient::deleteObject] HTTP " << response.status_code << ": " << response.error.message << std::endl; + if (!response.text.empty()) { + std::cerr << "[GetbinClient::deleteObject] Response: " << response.text << std::endl; + } + } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::deleteObject] Exception: " << e.what() << std::endl; + return false; + } +} + +bool GetbinClient::listPackages(std::vector& outPackages) { + try { + std::string url = "https://" + SERVER_HOST + "/packages"; + + auto response = cpr::Get(cpr::Url{url}, + cpr::Header{{"User-Agent", getUserAgent()}}, + cpr::Timeout{30000}, // 30 seconds + cpr::VerifySsl{true}); + + if (response.status_code == 200) { + try { + auto resp_json = json::parse(response.text); + if (resp_json.is_array()) { + outPackages.clear(); + for (const auto& item : resp_json) { + if (item.is_string()) { + outPackages.push_back(item.get()); + } + } + return true; + } else if (resp_json.contains("packages") && resp_json["packages"].is_array()) { + outPackages.clear(); + for (const auto& item : resp_json["packages"]) { + if (item.is_string()) { + outPackages.push_back(item.get()); + } + } + return true; + } + } catch (const json::exception&) { + // Try to parse as newline-separated list + outPackages.clear(); + std::istringstream stream(response.text); + std::string line; + while (std::getline(stream, line)) { + if (!line.empty()) { + outPackages.push_back(line); + } + } + return !outPackages.empty(); + } + } else { + std::cerr << "[GetbinClient::listPackages] HTTP " << response.status_code << ": " << response.error.message << std::endl; + } + + return false; + } catch (const std::exception& e) { + std::cerr << "[GetbinClient::listPackages] Exception: " << e.what() << std::endl; + return false; + } +} \ No newline at end of file diff --git a/getpkg/src/GetbinClient.hpp b/getpkg/src/GetbinClient.hpp index 9bd2a56..d45eca1 100644 --- a/getpkg/src/GetbinClient.hpp +++ b/getpkg/src/GetbinClient.hpp @@ -1,13 +1,24 @@ #pragma once #include #include +#include class GetbinClient { public: GetbinClient(); - bool download(const std::string& toolName, const std::string& arch, const std::string& outPath); - bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token); + + // Progress callback: (downloaded_bytes, total_bytes) -> should_continue + using ProgressCallback = std::function; + + bool download(const std::string& toolName, const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback = nullptr); + bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token, + ProgressCallback progressCallback = nullptr); bool getHash(const std::string& toolName, const std::string& arch, std::string& outHash); bool deleteObject(const std::string& hash, const std::string& token); bool listPackages(std::vector& outPackages); + +private: + static const std::string SERVER_HOST; + std::string getUserAgent() const; }; \ No newline at end of file diff --git a/getpkg/src/main.cpp b/getpkg/src/main.cpp index 1e2a469..d4b23f2 100644 --- a/getpkg/src/main.cpp +++ b/getpkg/src/main.cpp @@ -200,27 +200,43 @@ int install_tool(int argc, char* argv[]) { // Download tool - try arch-specific version first, then universal fallback GetbinClient getbin2; std::string downloadArch = arch; - //std::cout << "Downloading " << toolName << ":" << arch << "..." << std::endl; - if (!getbin2.download(toolName, arch, archivePath.string())) { + + // Progress callback for downloads + auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool { + if (total > 0) { + int percent = (downloaded * 100) / total; + std::cout << "\rDownloading " << toolName << "... " << percent << "%" << std::flush; + } else { + std::cout << "\rDownloading " << toolName << "... " << downloaded << " bytes" << std::flush; + } + return true; // Continue download + }; + + std::cout << "Downloading " << toolName << "..." << std::flush; + if (!getbin2.download(toolName, arch, archivePath.string(), progressCallback)) { // Try universal version as fallback - //std::cout << "Arch-specific version not found, trying universal version..." << std::endl; - //std::cout << "Downloading " << toolName << ":universal..." << std::endl; - if (!getbin2.download(toolName, "universal", archivePath.string())) { - std::cerr << "Failed to download tool archive (tried both " << arch << " and universal)." << std::endl; + std::cout << "\rArch-specific version not found, trying universal..." << std::endl; + if (!getbin2.download(toolName, "universal", archivePath.string(), progressCallback)) { + std::cerr << "\rFailed to download tool archive (tried both " << arch << " and universal)." << std::endl; return 1; } downloadArch = "universal"; } + std::cout << "\rDownloading " << toolName << "... done" << std::endl; // Unpack tool + std::cout << "Unpacking..." << std::flush; if (!common::unpack_tgz(archivePath.string(), binDir.string())) { - std::cerr << "Failed to unpack tool archive." << std::endl; + std::cerr << "\rFailed to unpack tool archive." << std::endl; return 1; } + std::cout << "\rUnpacking... done" << std::endl; // Add to PATH and autocomplete + std::cout << "Configuring..." << std::flush; scriptManager.addToolEntry(toolName, binDir.string()); scriptManager.addAutocomplete(toolName); + std::cout << "\rConfiguring... done" << std::endl; // Get tool info std::string hash; @@ -314,10 +330,24 @@ int publish_tool(int argc, char* argv[]) { } GetbinClient getbin; std::string url, hash; - if (!getbin.upload(archivePath.string(), url, hash, token)) { - std::cerr << "Failed to upload archive." << std::endl; + + // Progress callback for upload + auto uploadProgressCallback = [](size_t uploaded, size_t total) -> bool { + if (total > 0) { + int percent = (uploaded * 100) / total; + std::cout << "\rUploading... " << percent << "%" << std::flush; + } else { + std::cout << "\rUploading... " << uploaded << " bytes" << std::flush; + } + return true; // Continue upload + }; + + std::cout << "Uploading..." << std::flush; + if (!getbin.upload(archivePath.string(), url, hash, token, uploadProgressCallback)) { + std::cerr << "\rFailed to upload archive." << std::endl; return 1; } + std::cout << "\rUploading... done" << std::endl; std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl; return 0; } diff --git a/getpkg/test_debug/debug-test b/getpkg/test_debug/debug-test new file mode 100755 index 0000000..25c92a3 --- /dev/null +++ b/getpkg/test_debug/debug-test @@ -0,0 +1 @@ +#!/bin/bash\necho debug diff --git a/getpkg/test_debug2/debug-test2 b/getpkg/test_debug2/debug-test2 new file mode 100755 index 0000000..6b478e2 --- /dev/null +++ b/getpkg/test_debug2/debug-test2 @@ -0,0 +1 @@ +#!/bin/bash\necho debug2 diff --git a/getpkg/test_upload.txt b/getpkg/test_upload.txt new file mode 100644 index 0000000..d670460 --- /dev/null +++ b/getpkg/test_upload.txt @@ -0,0 +1 @@ +test content