diff --git a/.kiro/specs/multi-server-support/tasks.md b/.kiro/specs/multi-server-support/tasks.md index c9045b0..0c18612 100644 --- a/.kiro/specs/multi-server-support/tasks.md +++ b/.kiro/specs/multi-server-support/tasks.md @@ -4,7 +4,10 @@ Based on analysis of the current codebase, the multi-server support feature need ## Core Infrastructure Tasks -- [-] 1. Create ServerManager class and server configuration system +- [x] 1. Create ServerManager class and server configuration system + + + @@ -15,6 +18,10 @@ Based on analysis of the current codebase, the multi-server support feature need - _Requirements: 1.1, 1.2, 1.3, 5.1, 5.2, 5.4_ - [ ] 2. Enhance GetbinClient for multi-server support + + + + - Modify GetbinClient constructor to accept server list instead of hardcoded host - Implement multi-server fallback logic for downloads - Add server-specific upload and hash operations diff --git a/getpkg/src/GetbinClient.cpp b/getpkg/src/GetbinClient.cpp index 3f5e1e3..714a04f 100644 --- a/getpkg/src/GetbinClient.cpp +++ b/getpkg/src/GetbinClient.cpp @@ -10,20 +10,37 @@ using json = nlohmann::json; -const std::string GetbinClient::SERVER_HOST = "getpkg.xyz"; +const std::string GetbinClient::DEFAULT_SERVER_HOST = "getpkg.xyz"; -GetbinClient::GetbinClient() { +GetbinClient::GetbinClient(const std::vector& servers) : servers_(servers) { // Initialize CPR (done automatically, but we could add global config here) + if (servers_.empty()) { + servers_.push_back(DEFAULT_SERVER_HOST); + } +} + +GetbinClient::GetbinClient() : servers_({DEFAULT_SERVER_HOST}) { + // Backward compatibility constructor } 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) { +std::string GetbinClient::buildUrl(const std::string& serverUrl, const std::string& endpoint) const { + std::string url = "https://" + serverUrl; + if (!endpoint.empty() && endpoint[0] != '/') { + url += "/"; + } + url += endpoint; + return url; +} + +bool GetbinClient::downloadFromServer(const std::string& serverUrl, const std::string& toolName, + const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback) { try { - std::string url = "https://" + SERVER_HOST + "/object/" + toolName + ":" + arch; + std::string url = buildUrl(serverUrl, "/object/" + toolName + ":" + arch); cpr::Session session; session.SetUrl(cpr::Url{url}); @@ -52,20 +69,34 @@ bool GetbinClient::download(const std::string& toolName, const std::string& arch // Not found - this is expected for arch fallback return false; } else { - std::cerr << "[GetbinClient::download] HTTP " << response.status_code << ": " << response.error.message << std::endl; + std::cerr << "[GetbinClient::downloadFromServer] HTTP " << response.status_code << " from " << serverUrl << ": " << response.error.message << std::endl; } return false; } catch (const std::exception& e) { - std::cerr << "[GetbinClient::download] Exception: " << e.what() << std::endl; + std::cerr << "[GetbinClient::downloadFromServer] Exception with " << serverUrl << ": " << 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) { +bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback) { + // Multi-server fallback logic: try each server in order + for (const auto& server : servers_) { + if (downloadFromServer(server, toolName, arch, outPath, progressCallback)) { + return true; + } + } + + // If we get here, no server had the package + return false; +} + +bool GetbinClient::upload(const std::string& serverUrl, const std::string& archivePath, + std::string& outUrl, std::string& outHash, const std::string& token, + ProgressCallback progressCallback) { try { - std::string url = "https://" + SERVER_HOST + "/upload"; + std::string url = buildUrl(serverUrl, "/upload"); cpr::Session session; session.SetUrl(cpr::Url{url}); @@ -110,7 +141,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s 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(); + outUrl = buildUrl(serverUrl, "/object/" + resp_json["hash"].get()); outHash = resp_json["hash"].get(); return true; } @@ -125,7 +156,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s return !outHash.empty(); } } else { - std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << ": " << response.error.message << std::endl; + std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << " to " << serverUrl << ": " << response.error.message << std::endl; if (!response.text.empty()) { std::cerr << "[GetbinClient::upload] Response: " << response.text << std::endl; } @@ -133,14 +164,24 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s return false; } catch (const std::exception& e) { - std::cerr << "[GetbinClient::upload] Exception: " << e.what() << std::endl; + std::cerr << "[GetbinClient::upload] Exception with " << serverUrl << ": " << e.what() << std::endl; return false; } } -bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { +bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, + const std::string& token, ProgressCallback progressCallback) { + // Backward compatibility: use first server + if (servers_.empty()) { + return false; + } + return upload(servers_[0], archivePath, outUrl, outHash, token, progressCallback); +} + +bool GetbinClient::getHash(const std::string& serverUrl, const std::string& toolName, + const std::string& arch, std::string& outHash) { try { - std::string url = "https://" + SERVER_HOST + "/hash/" + toolName + ":" + arch; + std::string url = buildUrl(serverUrl, "/hash/" + toolName + ":" + arch); auto response = cpr::Get(cpr::Url{url}, cpr::Header{{"User-Agent", getUserAgent()}}, @@ -168,19 +209,63 @@ bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, // 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; + std::cerr << "[GetbinClient::getHash] HTTP " << response.status_code << " from " << serverUrl << ": " << response.error.message << std::endl; } return false; } catch (const std::exception& e) { - std::cerr << "[GetbinClient::getHash] Exception: " << e.what() << std::endl; + std::cerr << "[GetbinClient::getHash] Exception with " << serverUrl << ": " << e.what() << std::endl; return false; } } +bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { + // Multi-server fallback: try each server in order + for (const auto& server : servers_) { + if (getHash(server, toolName, arch, outHash)) { + return true; + } + } + + // If we get here, no server had the package + return false; +} + +bool GetbinClient::findPackageServer(const std::string& toolName, const std::string& arch, + std::string& foundServer) const { + // Check each server to see which one has the package + for (const auto& server : servers_) { + try { + std::string url = buildUrl(server, "/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) { + // Package found on this server + foundServer = server; + return true; + } + // Continue to next server if 404 or other error + } catch (const std::exception& e) { + // Continue to next server on exception + std::cerr << "[GetbinClient::findPackageServer] Exception with " << server << ": " << e.what() << std::endl; + } + } + + // Package not found on any server + return false; +} + bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) { try { - std::string url = "https://" + SERVER_HOST + "/deleteobject?hash=" + hash; + // Use first server for backward compatibility + if (servers_.empty()) { + return false; + } + std::string url = buildUrl(servers_[0], "/deleteobject?hash=" + hash); auto response = cpr::Get(cpr::Url{url}, cpr::Header{ @@ -208,7 +293,11 @@ bool GetbinClient::deleteObject(const std::string& hash, const std::string& toke bool GetbinClient::listPackages(std::vector& outPackages) { try { - std::string url = "https://" + SERVER_HOST + "/dir"; + // Use first server for backward compatibility + if (servers_.empty()) { + return false; + } + std::string url = buildUrl(servers_[0], "/dir"); auto response = cpr::Get(cpr::Url{url}, cpr::Header{{"User-Agent", getUserAgent()}}, @@ -271,7 +360,11 @@ bool GetbinClient::listPackages(std::vector& outPackages) { bool GetbinClient::listAllEntries(std::vector>>& outEntries) { try { - std::string url = "https://" + SERVER_HOST + "/dir"; + // Use first server for backward compatibility + if (servers_.empty()) { + return false; + } + std::string url = buildUrl(servers_[0], "/dir"); auto response = cpr::Get(cpr::Url{url}, cpr::Header{{"User-Agent", getUserAgent()}}, diff --git a/getpkg/src/GetbinClient.hpp b/getpkg/src/GetbinClient.hpp index 9208e99..4296954 100644 --- a/getpkg/src/GetbinClient.hpp +++ b/getpkg/src/GetbinClient.hpp @@ -5,21 +5,53 @@ class GetbinClient { public: + // Constructor accepting server list for multi-server support + GetbinClient(const std::vector& servers); + + // Backward compatibility constructor (uses default server) GetbinClient(); // Progress callback: (downloaded_bytes, total_bytes) -> should_continue using ProgressCallback = std::function; + // Multi-server download with fallback logic bool download(const std::string& toolName, const std::string& arch, const std::string& outPath, ProgressCallback progressCallback = nullptr); + + // Server-specific download + bool downloadFromServer(const std::string& serverUrl, const std::string& toolName, + const std::string& arch, const std::string& outPath, + ProgressCallback progressCallback = nullptr); + + // Server-specific upload + bool upload(const std::string& serverUrl, const std::string& archivePath, + std::string& outUrl, std::string& outHash, const std::string& token, + ProgressCallback progressCallback = nullptr); + + // Backward compatibility upload (uses first server) bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token, ProgressCallback progressCallback = nullptr); + + // Server-specific hash retrieval + bool getHash(const std::string& serverUrl, const std::string& toolName, + const std::string& arch, std::string& outHash); + + // Multi-server hash retrieval with fallback bool getHash(const std::string& toolName, const std::string& arch, std::string& outHash); + + // Find which server has a specific package + bool findPackageServer(const std::string& toolName, const std::string& arch, + std::string& foundServer) const; + + // Legacy methods (use first server for backward compatibility) bool deleteObject(const std::string& hash, const std::string& token); bool listPackages(std::vector& outPackages); bool listAllEntries(std::vector>>& outEntries); private: - static const std::string SERVER_HOST; + static const std::string DEFAULT_SERVER_HOST; + std::vector servers_; + std::string getUserAgent() const; + std::string buildUrl(const std::string& serverUrl, const std::string& endpoint) const; };