diff --git a/README.md b/README.md index d46659d..7cbff0b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ yns upgrade # 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 diff --git a/include/package_manager.hpp b/include/package_manager.hpp index bf3421a..2b4da83 100644 --- a/include/package_manager.hpp +++ b/include/package_manager.hpp @@ -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; diff --git a/src/main.cpp b/src/main.cpp index 92a50bf..aaf2db2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,9 @@ void print_usage() { << " upgrade 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 [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 [package_name]" << std::endl; + std::cout << "\nCommands:\n"; + std::cout << " update Update package cache\n"; + std::cout << " install Install a package\n"; + std::cout << " remove Remove a package\n"; + std::cout << " upgrade 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; } \ No newline at end of file diff --git a/src/package_manager.cpp b/src/package_manager.cpp index 9a7e97d..8c52f31 100644 --- a/src/package_manager.cpp +++ b/src/package_manager.cpp @@ -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,18 +184,19 @@ 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"; @@ -187,8 +206,9 @@ bool PackageManager::install(const std::string& package_name) { print_error("Failed to download installation script"); 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()); @@ -204,6 +224,8 @@ bool PackageManager::install(const std::string& package_name) { print_error("Installation failed with exit code: " + std::to_string(status)); return false; } + + print_progress("Installing " + package_name + "@" + repo_version, 100); installed_packages[package_name] = { {"version", repo_version} @@ -219,6 +241,12 @@ bool PackageManager::remove(const std::string& package_name) { print_error("Package '" + package_name + "' is not installed"); 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)) { @@ -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()); @@ -251,6 +280,8 @@ bool PackageManager::remove(const std::string& package_name) { print_error("Removal failed with exit code: " + std::to_string(status)); return false; } + + print_progress("Removing " + package_name, 100); installed_packages.erase(package_name); save_installed_db(installed_packages); @@ -447,4 +478,101 @@ 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"); + } } \ No newline at end of file