diff --git a/src/rppi_get.cpp b/src/rppi_get.cpp index 14ed250..51e8023 100644 --- a/src/rppi_get.cpp +++ b/src/rppi_get.cpp @@ -444,6 +444,48 @@ int clone_or_update_repository(const char *repo_url, const char *local_path, } // libgit2 relate functions ends // ---------------------------------------------------------------------------- +// dump json object to a file +int write_json(const json &j, const std::string &file) { + std::ofstream output(file); + if (!output.is_open()) { + std::cerr << "Failed to open output file: " << file << std::endl; + return 1; + } + output << j.dump(4); + output.close(); + return 0; +} +// load a json file +json load_json(const std::string &file_path, bool create = false) { + if (!std::filesystem::exists(file_path)) { + if (create) + write_json(json(), file_path); + return json(); + } + std::ifstream file(file_path); + json j; + if (file.peek() != std::ifstream::traits_type::eof()) + file >> j; + else { + file.close(); + if (create) + write_json(j, file_path); + return j; + } + file.close(); + return j; +} +bool json_array_contain(const json &j, const std::string &str) { + bool exists = false; + for (auto i = 0; i < j.size(); i++) { + if (j[i] == str) { + exists = true; + break; + } + } + return exists; +} +// ---------------------------------------------------------------------------- // recipes processes // ---------------------------------------------------------------------------- // install recipe repo @@ -454,18 +496,34 @@ int install_recipe(const Recipe &recipe, const std::string &recipe_file = "") { VString files; int error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), proxy.c_str()); + if (error) { + std::cerr << "clone_or_update_repository " << repo_url << " to " + << local_path << " failed!" << std::endl; + return error; + } + // load cache_dir/.installed_recipes.json, create one if not exists + json installed_recipes = + load_json(parse_path(cache_dir + sep + ".installed_recipes.json"), true); + std::string recipe_file_path = recipe_file.empty() ? "" : local_path + sep + recipe_file; if (!file_exist(recipe_file_path)) recipe_file_path = ""; list_files_to_vector(std::filesystem::path(local_path), files, recipe_file_path); + if (recipe_file.empty() && installed_recipes.contains(recipe.local_path)) + installed_recipes.erase(recipe.local_path); + for (const auto &file : files) { std::string target_path = user_dir + sep + std::filesystem::relative(file, local_path).string(); copy_file(file, target_path); target_path = convertToUtf8(target_path); std::cout << "installed: " << target_path << std::endl; + bool exists = + json_array_contain(installed_recipes[recipe.local_path], target_path); + if (!exists) + installed_recipes[recipe.local_path].push_back(target_path); } VString().swap(files); if (recipe.dependencies.size()) { @@ -478,6 +536,8 @@ int install_recipe(const Recipe &recipe, const std::string &recipe_file = "") { error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), proxy.c_str()); list_files_to_vector(std::filesystem::path(local_path), files); + if (installed_recipes.contains(dep.substr(pos + 1))) + installed_recipes.erase(dep.substr(pos + 1)); for (const auto &file : files) { std::string target_path = user_dir + sep + @@ -485,6 +545,10 @@ int install_recipe(const Recipe &recipe, const std::string &recipe_file = "") { copy_file(file, target_path); target_path = convertToUtf8(target_path); std::cout << "installed: " << target_path << std::endl; + bool exists = json_array_contain(installed_recipes[dep.substr(pos + 1)], + target_path); + if (!exists) + installed_recipes[dep.substr(pos + 1)].push_back(target_path); } VString().swap(files); } @@ -499,6 +563,8 @@ int install_recipe(const Recipe &recipe, const std::string &recipe_file = "") { error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), proxy.c_str()); list_files_to_vector(std::filesystem::path(local_path), files); + if (installed_recipes.contains(dep.substr(pos + 1))) + installed_recipes.erase(dep.substr(pos + 1)); for (const auto &file : files) { std::string target_path = user_dir + sep + @@ -506,10 +572,132 @@ int install_recipe(const Recipe &recipe, const std::string &recipe_file = "") { copy_file(file, target_path); target_path = convertToUtf8(target_path); std::cout << "installed: " << target_path << std::endl; + bool exists = json_array_contain(installed_recipes[dep.substr(pos + 1)], + target_path); + if (!exists) + installed_recipes[dep.substr(pos + 1)].push_back(target_path); + } + VString().swap(files); + } + } + write_json(installed_recipes, + parse_path(cache_dir + sep + ".installed_recipes.json")); + return error; +} +// delete recipe repo +int delete_recipe(const Recipe &recipe, const std::string &recipe_file = "", + bool purge = false) { + std::string repo_url = mirror + recipe.repo + ".git"; + std::string local_path = (cache_dir + sep + recipe.local_path); + std::cout << "update recipe : " << recipe.repo << std::endl; + VString files; + int error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), + proxy.c_str()); + if (error) { + std::cerr << "clone_or_update_repository " << repo_url << " to " + << local_path << " failed!" << std::endl; + return error; + } + // load cache_dir/.installed_recipes.json, create one if not exists + json installed_recipes = + load_json(parse_path(cache_dir + sep + ".installed_recipes.json"), true); + + std::string recipe_file_path = + recipe_file.empty() ? "" : local_path + sep + recipe_file; + if (!file_exist(recipe_file_path)) + recipe_file_path = ""; + list_files_to_vector(std::filesystem::path(local_path), files, + recipe_file_path); + for (const auto &file : files) { + std::string target_path = + user_dir + sep + std::filesystem::relative(file, local_path).string(); + if (file_exist(target_path)) { + delete_file(target_path); + target_path = convertToUtf8(target_path); + std::cout << "deleted: " << target_path << std::endl; + } + for (auto it = installed_recipes[recipe.local_path].begin(); + it != installed_recipes[recipe.local_path].end(); ++it) { + if (*it == target_path) { + it = installed_recipes[recipe.local_path].erase(it); + break; + } + } + } + if (installed_recipes[recipe.local_path].empty()) + installed_recipes.erase(recipe.local_path); + VString().swap(files); + if (!purge) { + write_json(installed_recipes, + parse_path(cache_dir + sep + ".installed_recipes.json")); + return 0; + } + if (recipe.dependencies.size()) { + for (const auto &dep : recipe.dependencies) { + repo_url = mirror + dep + ".git"; + size_t pos = dep.find("/"); + std::string local_path = dep.substr(pos + 1); + local_path = (cache_dir + sep + local_path); + std::cout << "update dependency : " << dep << std::endl; + error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), + proxy.c_str()); + list_files_to_vector(std::filesystem::path(local_path), files); + for (const auto &file : files) { + std::string target_path = + user_dir + sep + + std::filesystem::relative(file, local_path).string(); + if (file_exist(target_path)) { + delete_file(target_path); + target_path = convertToUtf8(target_path); + std::cout << "deleted: " << target_path << std::endl; + } + for (auto it = installed_recipes[dep.substr(pos + 1)].begin(); + it != installed_recipes[dep.substr(pos + 1)].end(); ++it) { + if (*it == target_path) { + it = installed_recipes[dep.substr(pos + 1)].erase(it); + break; + } + } + } + if (installed_recipes.contains(dep.substr(pos + 1))) + installed_recipes.erase(dep.substr(pos + 1)); + VString().swap(files); + } + } + if (recipe.reverseDependencies.size()) { + for (const auto &dep : recipe.reverseDependencies) { + repo_url = mirror + dep + ".git"; + size_t pos = dep.find("/"); + std::string local_path = dep.substr(pos + 1); + local_path = (cache_dir + sep + local_path); + std::cout << "update reverseDependency : " << dep << std::endl; + error = clone_or_update_repository(repo_url.c_str(), local_path.c_str(), + proxy.c_str()); + list_files_to_vector(std::filesystem::path(local_path), files); + for (const auto &file : files) { + std::string target_path = + user_dir + sep + + std::filesystem::relative(file, local_path).string(); + if (file_exist(target_path)) { + delete_file(target_path); + target_path = convertToUtf8(target_path); + std::cout << "deleted: " << target_path << std::endl; + } + for (auto it = installed_recipes[dep.substr(pos + 1)].begin(); + it != installed_recipes[dep.substr(pos + 1)].end(); ++it) { + if (*it == target_path) { + it = installed_recipes[dep.substr(pos + 1)].erase(it); + break; + } + } } VString().swap(files); + if (installed_recipes.contains(dep.substr(pos + 1))) + installed_recipes.erase(dep.substr(pos + 1)); } } + write_json(installed_recipes, + parse_path(cache_dir + sep + ".installed_recipes.json")); return error; } // update rppi cache @@ -518,14 +706,6 @@ int update_rppi(std::string local_path, std::string mirror, std::string proxy) { return clone_or_update_repository(repo_url.c_str(), local_path.c_str(), proxy.empty() ? nullptr : proxy.c_str()); } -// load a json file -json load_json(const std::string &file_path) { - std::ifstream file(file_path); - json j; - file >> j; - file.close(); - return j; -} // parse rppi index.json to get Recipe vector void parse_index_rppi(const std::string &file_dir, const std::string &category, std::vector &recipes) { @@ -657,9 +837,14 @@ int main(int argc, char **argv) { return 0; try { cxxopts::Options options("rppi_get", " - A toy to play with rppi"); - options.add_options()("h,help", "print help")("u,update", "update rppi")( - "i,install", "install recipe", cxxopts::value())( - "g,git", "install recipe by git repo", cxxopts::value())( + options.add_options()("h,help", "print help")( + "I,installed", "list recipes installed")("u,update", "update rppi")( + "i,install", "install or update a recipe", + cxxopts::value())("d,delete", "delete a recipe", + cxxopts::value())( + "P,purge", "purge a recipe (with dependencies and reverseDependencies)", + cxxopts::value())("g,git", "install recipe by git repo", + cxxopts::value())( "s,search", "search recipe with keyword", cxxopts::value())("c,clean", "clean caches")( "v,verbose", "verbose settings")("l,list", "list recipes in rppi")( @@ -710,9 +895,10 @@ int main(int argc, char **argv) { for (const auto &entry : std::filesystem::directory_iterator(directory)) { if (entry.is_directory() && entry.path().filename() != "." && - entry.path().filename() != "..") + entry.path().filename() != "..") { std::cout << "delete directory: " << entry.path() << std::endl; - delete_directory(entry.path().string()); + delete_directory(entry.path().string()); + } } return 0; } else if (result.count("update")) { @@ -728,6 +914,13 @@ int main(int argc, char **argv) { } if (retry > 10) return 0; + } else if (result.count("installed")) { + std::cout << "recipes installed:" << std::endl; + json j = + load_json(parse_path(cache_dir + sep + ".installed_recipes.json")); + for (auto it = j.begin(); it != j.end(); ++it) + std::cout << "[*] " << it.key() << std::endl; + return 0; } std::vector recipes; if (file_exist(cache_dir + "/rppi/index.json")) { @@ -796,6 +989,44 @@ int main(int argc, char **argv) { print_recipes(res); std::vector().swap(recipes); return 0; + } else if (result.count("delete")) { + std::string repo = result["delete"].as(); + repo = convertToUtf8(repo); + size_t pos = repo.find(':'); + std::string recipe_file = ""; + if (pos < repo.length()) { + recipe_file = repo.substr(pos + 1) + ".recipe.yaml"; + repo = repo.substr(0, pos); + } + std::vector res = + filter_recipes_with_keyword(recipes, repo, true); + if (res.size()) { + std::cout << "delete recipe by keyword : " << repo; + if (!recipe_file.empty()) + std::cout << ", with recipe file: " << recipe_file; + std::cout << std::endl; + delete_recipe(res.at(0), recipe_file); + } else { + std::cout << "delete recipe by : " << result["delete"].as() + << " failed ||-_-" << std::endl; + } + std::vector().swap(recipes); + return 0; + } else if (result.count("purge")) { + std::string repo = result["purge"].as(); + repo = convertToUtf8(repo); + std::vector res = + filter_recipes_with_keyword(recipes, repo, true); + if (res.size()) { + std::cout << "purge recipe by keyword : " << repo; + std::cout << std::endl; + delete_recipe(res.at(0), "", true); + } else { + std::cout << "purge recipe by : " << result["purge"].as() + << " failed ||-_-" << std::endl; + } + std::vector().swap(recipes); + return 0; } } catch (const std::exception &e) { std::cerr << "Error parsing options: " << e.what() << std::endl;