'Generic Commit'
All checks were successful
Build-Test-Publish / build (linux/amd64) (push) Successful in 45s
Build-Test-Publish / build (linux/arm64) (push) Successful in 1m3s

This commit is contained in:
Your Name 2025-06-22 11:59:09 +12:00
parent ab87fecb39
commit c4001b0efc
3 changed files with 236 additions and 1 deletions

View File

@ -15,6 +15,8 @@
#include <condition_variable>
#include <vector>
#include <ctime>
#include <algorithm>
#include <set>
using json = nlohmann::json;
@ -377,3 +379,123 @@ bool GetbinClient::deleteObject(const std::string& hash, const std::string& toke
worker.join();
return success;
}
bool GetbinClient::listPackages(std::vector<std::string>& outPackages) {
outPackages.clear();
// Set up SSL configuration
std::string ca_path = find_ca_certificates();
if (!ca_path.empty()) {
std::cout << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
}
bool success = false;
bool done = false;
std::mutex mtx;
std::condition_variable cv;
std::thread worker([&]() {
trantor::EventLoop loop;
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST), &loop, true, false);
if (!ca_path.empty()) {
std::vector<std::pair<std::string, std::string>> sslConfigs;
sslConfigs.push_back({"VerifyCAFile", ca_path});
client->addSSLConfigs(sslConfigs);
}
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<std::string>();
// 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;
}
} 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;
}
} 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<std::mutex> 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<std::string> filteredPackages;
std::set<std::string> 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
}
} else {
filteredPackages.push_back(pkg); // Always keep base versions
}
}
outPackages = std::move(filteredPackages);
// Sort the packages for better display
std::sort(outPackages.begin(), outPackages.end());
return success;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
class GetbinClient {
public:
@ -8,4 +9,5 @@ public:
bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token);
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<std::string>& outPackages);
};

View File

@ -63,9 +63,11 @@
#include <string>
#include <vector>
#include <set>
#include <map>
#include <filesystem>
#include <fstream>
#include <cstring>
#include <iomanip>
#include <nlohmann/json.hpp>
namespace {
@ -486,6 +488,109 @@ int unpublish_tool(int argc, char* argv[]) {
}
}
int list_packages(int argc, char* argv[]) {
GetbinClient getbin;
std::vector<std::string> availablePackages;
std::cout << "Fetching package list from getpkg.xyz..." << std::endl;
if (!getbin.listPackages(availablePackages)) {
std::cerr << "Failed to fetch package list from server." << std::endl;
return 1;
}
if (availablePackages.empty()) {
std::cout << "No packages found on server." << std::endl;
return 0;
}
// Get local installation status
std::string home = get_home();
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
std::filesystem::path getpkgDir = std::filesystem::path(home) / ".getpkg";
// Build list of installed packages with their info
std::map<std::string, json> installedPackages;
if (std::filesystem::exists(configDir)) {
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
if (entry.path().extension() == ".json") {
std::string toolName = entry.path().stem().string();
try {
std::ifstream configFile(entry.path());
json toolConfig;
configFile >> toolConfig;
installedPackages[toolName] = toolConfig;
} catch (...) {
// Skip malformed config files
}
}
}
}
// Print header
std::cout << std::endl;
std::cout << "+" << std::string(25, '-') << "+" << std::string(12, '-') << "+" << std::string(18, '-') << "+" << std::string(10, '-') << "+" << std::endl;
std::cout << "|" << std::setw(25) << std::left << " Package"
<< "|" << std::setw(12) << std::left << " Status"
<< "|" << std::setw(18) << std::left << " Local Version"
<< "|" << std::setw(10) << std::left << " Latest"
<< "|" << std::endl;
std::cout << "+" << std::string(25, '-') << "+" << std::string(12, '-') << "+" << std::string(18, '-') << "+" << std::string(10, '-') << "+" << std::endl;
// Process each package
for (const auto& packageName : availablePackages) {
std::string status = "Available";
std::string localVersion = "-";
std::string remoteStatus = "";
auto it = installedPackages.find(packageName);
if (it != installedPackages.end()) {
// Package is installed
json& config = it->second;
localVersion = config.value("version", "unknown");
std::string localHash = config.value("hash", "");
std::string arch = config.value("arch", get_arch());
// Check if it's up to date by comparing hashes
std::string remoteHash;
if (getbin.getHash(packageName, arch, remoteHash) && !remoteHash.empty()) {
if (localHash == remoteHash) {
status = "✓ Latest";
remoteStatus = "";
} else {
status = "⚡ Update";
remoteStatus = "";
}
} else {
status = "✓ Local";
remoteStatus = "?";
}
}
// Print row
std::cout << "|" << std::setw(25) << std::left << (" " + packageName).substr(0, 24)
<< "|" << std::setw(12) << std::left << (" " + status).substr(0, 11)
<< "|" << std::setw(18) << std::left << (" " + localVersion).substr(0, 17)
<< "|" << std::setw(10) << std::left << (" " + remoteStatus).substr(0, 9)
<< "|" << std::endl;
}
// Print footer
std::cout << "+" << std::string(25, '-') << "+" << std::string(12, '-') << "+" << std::string(18, '-') << "+" << std::string(10, '-') << "+" << std::endl;
std::cout << std::endl;
// Print legend
std::cout << "Legend:" << std::endl;
std::cout << " Available - Package available for installation" << std::endl;
std::cout << " ✓ Latest - Installed and up to date" << std::endl;
std::cout << " ⚡ Update - Installed but update available" << std::endl;
std::cout << " ✓ Local - Installed (remote status unknown)" << std::endl;
std::cout << std::endl;
std::cout << "Total packages: " << availablePackages.size()
<< ", Installed: " << installedPackages.size() << std::endl;
return 0;
}
int clean_tool(int argc, char* argv[]) {
std::string home = get_home();
std::filesystem::path getpkgDir = std::filesystem::path(home) / ".getpkg";
@ -699,6 +804,9 @@ void show_help() {
std::cout << " hash <file_or_directory> Calculate hash of file or directory" << std::endl;
std::cout << " Outputs raw hash value to stdout" << std::endl;
std::cout << std::endl;
std::cout << " list List all available packages" << std::endl;
std::cout << " Shows installation status and version information" << std::endl;
std::cout << std::endl;
std::cout << " clean Clean up orphaned configs and symlinks" << std::endl;
std::cout << " Removes unused config files and dangling symlinks" << std::endl;
std::cout << std::endl;
@ -752,6 +860,7 @@ update
version
create
hash
list
clean
help
)";
@ -761,6 +870,8 @@ help
return create_tool(argc, argv);
} else if (command == "hash") {
return hash_command(argc, argv);
} else if (command == "list") {
return list_packages(argc, argv);
} else if (command == "clean") {
return clean_tool(argc, argv);
} else if (command == "help") {