add update funcionality and improve design a bit

This commit is contained in:
spitkov 2025-01-05 16:49:10 +01:00
parent 08d12cc656
commit fa794685ad
4 changed files with 188 additions and 66 deletions

View file

@ -40,6 +40,8 @@ yns upgrade <package> # Upgrade package
yns list # List packages
yns debug # Show debug info
yns interactive # Interactive mode
yns version # Show YNS version
yns updateyns # Update YNS to latest version
```
## Package Format

View file

@ -8,6 +8,8 @@ using json = nlohmann::json;
class PackageManager {
public:
static constexpr const char* VERSION = "1.1";
PackageManager();
bool update();
@ -17,6 +19,8 @@ public:
bool list();
bool interactive_mode();
bool debug();
void version();
void updateYns();
private:
static constexpr const char* REPO_URL = "https://raw.githubusercontent.com/spitkov/ynsrepo/refs/heads/main/repo.json";
@ -34,6 +38,7 @@ private:
void print_error(const std::string& message);
void print_success(const std::string& message);
void print_interactive_help();
bool confirm_action(const std::string& action);
json repo_cache;
json installed_packages;

View file

@ -13,7 +13,9 @@ void print_usage() {
<< " upgrade <package> Upgrade a package\n"
<< " list List all packages\n"
<< " debug Show debug information\n"
<< " interactive Start interactive mode\n\n"
<< " interactive Start interactive mode\n"
<< " version Show YNS version\n"
<< " updateyns Update YNS to latest version\n\n"
<< "Interactive Mode:\n"
<< " Run 'yns interactive' to enter interactive mode where you can\n"
<< " execute multiple commands without prefix. Type 'help' in\n"
@ -24,7 +26,7 @@ bool needs_sudo(const std::string& command) {
return command == "update" || command == "install" ||
command == "remove" || command == "upgrade" ||
command == "interactive" || command == "list" ||
command == "debug";
command == "debug" || command == "version" || command == "updateyns";
}
std::string get_self_path() {
@ -35,67 +37,52 @@ std::string get_self_path() {
int main(int argc, char* argv[]) {
if (argc < 2) {
print_usage();
std::cerr << "Error: No command provided" << std::endl;
std::cout << "Usage: yns <command> [package_name]" << std::endl;
return 1;
}
std::string command = argv[1];
if (needs_sudo(command) && geteuid() != 0) {
std::string self = get_self_path();
std::string sudo_cmd = "sudo " + self;
for (int i = 1; i < argc; i++) {
sudo_cmd += " ";
sudo_cmd += argv[i];
}
std::cout << "This operation requires root privileges.\n";
return system(sudo_cmd.c_str());
}
PackageManager pm;
try {
if (command == "update") {
return pm.update() ? 0 : 1;
}
else if (command == "list") {
return pm.list() ? 0 : 1;
}
else if (command == "debug") {
return pm.debug() ? 0 : 1;
}
else if (command == "interactive") {
return pm.interactive_mode() ? 0 : 1;
}
else if (command == "install" || command == "remove" || command == "upgrade") {
if (argc < 3) {
std::cerr << "Error: Package name required for " << command << " command\n";
print_usage();
return 1;
}
std::string package_name = argv[2];
if (command == "install") {
return pm.install(package_name) ? 0 : 1;
}
else if (command == "remove") {
return pm.remove(package_name) ? 0 : 1;
}
else {
return pm.upgrade(package_name) ? 0 : 1;
}
}
else {
std::cerr << "Error: Unknown command '" << command << "'\n";
print_usage();
pm.update();
} else if (command == "install" && argc == 3) {
pm.install(argv[2]);
} else if (command == "remove" && argc == 3) {
pm.remove(argv[2]);
} else if (command == "upgrade" && argc == 3) {
pm.upgrade(argv[2]);
} else if (command == "list") {
pm.list();
} else if (command == "debug") {
pm.debug();
} else if (command == "interactive") {
pm.interactive_mode();
} else if (command == "version") {
pm.version();
} else if (command == "updateyns") {
pm.updateYns();
} else {
std::cerr << "Error: Unknown command '" << command << "'" << std::endl;
std::cout << "Usage: yns <command> [package_name]" << std::endl;
std::cout << "\nCommands:\n";
std::cout << " update Update package cache\n";
std::cout << " install <package> Install a package\n";
std::cout << " remove <package> Remove a package\n";
std::cout << " upgrade <package> Upgrade a package\n";
std::cout << " list List all packages\n";
std::cout << " debug Show debug information\n";
std::cout << " interactive Start interactive mode\n";
std::cout << " version Show YNS version\n";
std::cout << " updateyns Update YNS to latest version\n";
return 1;
}
}
catch (const std::exception& e) {
std::cerr << "\033[31mError: " << e.what() << "\033[0m\n";
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View file

@ -15,7 +15,7 @@ const std::string BLUE = "\033[34m";
const std::string YELLOW = "\033[33m";
const std::string RESET = "\033[0m";
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
static size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp) {
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
@ -36,7 +36,7 @@ bool PackageManager::download_file(const std::string& url, const std::string& ou
std::string response_data;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
@ -132,7 +132,18 @@ void PackageManager::save_installed_db(const json& db) {
}
void PackageManager::print_progress(const std::string& message, int percentage) {
std::cout << BLUE << "[" << percentage << "%] " << message << RESET << std::endl;
const int bar_width = 50;
int filled_width = bar_width * percentage / 100;
std::cout << BLUE << "\r[";
for (int i = 0; i < bar_width; ++i) {
if (i < filled_width) std::cout << "=";
else if (i == filled_width) std::cout << ">";
else std::cout << " ";
}
std::cout << "] " << percentage << "% " << message << RESET;
std::cout.flush();
if (percentage == 100) std::cout << std::endl;
}
void PackageManager::print_error(const std::string& message) {
@ -147,6 +158,13 @@ bool PackageManager::update() {
return cache_repo();
}
bool PackageManager::confirm_action(const std::string& action) {
std::cout << YELLOW << "Do you want to " << action << "? [y/N] " << RESET;
std::string response;
std::getline(std::cin, response);
return (response == "y" || response == "Y");
}
bool PackageManager::install(const std::string& package_name) {
if (!update()) return false;
@ -166,19 +184,20 @@ bool PackageManager::install(const std::string& package_name) {
print_success(package_name + " is already installed (version " + installed_version + ")");
return true;
} else {
print_progress("New version available: " + repo_version + " (currently installed: " + installed_version + ")", 0);
std::cout << "Would you like to upgrade? [y/N] ";
std::string response;
std::getline(std::cin, response);
if (response == "y" || response == "Y") {
return upgrade(package_name);
} else {
std::cout << "\nNew version available: " + repo_version + " (currently installed: " + installed_version + ")\n";
if (!confirm_action("upgrade to version " + repo_version)) {
print_success("Keeping current version " + installed_version);
return true;
}
return upgrade(package_name);
}
}
if (!confirm_action("install " + package_name + " version " + repo_version)) {
std::cout << "Installation cancelled.\n";
return false;
}
std::string install_script = package["install"];
std::string temp_script = "/tmp/yns_install_" + package_name + ".sh";
print_progress("Downloading installation script", 0);
@ -188,7 +207,8 @@ bool PackageManager::install(const std::string& package_name) {
return false;
}
print_progress("Installing " + package_name + "@" + repo_version, 50);
print_progress("Downloading installation script", 100);
print_progress("Installing " + package_name + "@" + repo_version, 0);
std::string cmd = "chmod +x " + temp_script + " && " + temp_script;
int exit_code = system(cmd.c_str());
@ -205,6 +225,8 @@ bool PackageManager::install(const std::string& package_name) {
return false;
}
print_progress("Installing " + package_name + "@" + repo_version, 100);
installed_packages[package_name] = {
{"version", repo_version}
};
@ -220,6 +242,12 @@ bool PackageManager::remove(const std::string& package_name) {
return false;
}
std::string version = installed_packages[package_name]["version"];
if (!confirm_action("remove " + package_name + " version " + version)) {
std::cout << "Removal cancelled.\n";
return false;
}
repo_cache = read_cache();
if (!repo_cache["packages"].contains(package_name)) {
print_error("Package information not found in repository");
@ -235,7 +263,8 @@ bool PackageManager::remove(const std::string& package_name) {
return false;
}
print_progress("Removing " + package_name, 50);
print_progress("Downloading removal script", 100);
print_progress("Removing " + package_name, 0);
std::string cmd = "chmod +x " + temp_script + " && " + temp_script;
int exit_code = system(cmd.c_str());
@ -252,6 +281,8 @@ bool PackageManager::remove(const std::string& package_name) {
return false;
}
print_progress("Removing " + package_name, 100);
installed_packages.erase(package_name);
save_installed_db(installed_packages);
@ -448,3 +479,100 @@ bool PackageManager::debug() {
return true;
}
void PackageManager::version() {
std::cout << "YNS Package Manager version " << VERSION << std::endl;
}
void PackageManager::updateYns() {
std::cout << "Checking for YNS updates..." << std::endl;
CURL* curl = curl_easy_init();
if (!curl) {
print_error("Failed to initialize CURL");
return;
}
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/spitkov/ynspkg/releases/latest");
curl_easy_setopt(curl, CURLOPT_USERAGENT, "YNS Package Manager");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
print_progress("Checking for updates", 0);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
print_error("Failed to check for updates");
return;
}
try {
json release = json::parse(response);
std::string latest_version = release["tag_name"];
if (latest_version[0] == 'v') {
latest_version = latest_version.substr(1);
}
print_progress("Checking for updates", 100);
if (latest_version == VERSION) {
print_success("No new YNS version found. Latest version is " + std::string(VERSION));
return;
}
std::cout << "\nNew version " << latest_version << " found (current: " << VERSION << ")" << std::endl;
if (!confirm_action("update YNS to version " + latest_version)) {
std::cout << "Update cancelled.\n";
return;
}
print_progress("Downloading update", 0);
std::string download_url = release["assets"][0]["browser_download_url"];
std::string temp_file = "/tmp/yns_update";
curl = curl_easy_init();
FILE* fp = fopen(temp_file.c_str(), "wb");
if (!fp || !curl) {
print_error("Failed to prepare update");
if (fp) fclose(fp);
if (curl) curl_easy_cleanup(curl);
return;
}
curl_easy_setopt(curl, CURLOPT_URL, download_url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
fclose(fp);
curl_easy_cleanup(curl);
print_progress("Downloading update", 100);
if (res != CURLE_OK) {
print_error("Failed to download update");
return;
}
print_progress("Installing update", 0);
std::string cmd = "chmod +x " + temp_file + " && ";
cmd += "sudo cp " + temp_file + " /usr/bin/yns && ";
cmd += "sudo cp " + temp_file + " /bin/yns && ";
cmd += "rm " + temp_file;
if (system(cmd.c_str()) != 0) {
print_error("Failed to install update");
return;
}
print_progress("Installing update", 100);
print_success("Successfully updated to version " + latest_version);
} catch (const std::exception& e) {
print_error("Failed to parse update information");
}
}