diff --git a/include/clipp.h b/include/clipp.h deleted file mode 120000 index 5b53218..0000000 --- a/include/clipp.h +++ /dev/null @@ -1 +0,0 @@ -../modules/clipp/include/clipp.h \ No newline at end of file diff --git a/include/config.hpp b/include/config.hpp index b4df76b..bdfd0c5 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -22,6 +22,7 @@ namespace gdpm::config{ table = 1, }; + struct context{ string username; string password; @@ -44,6 +45,8 @@ namespace gdpm::config{ rest_api::request_params rest_api_params; }; + string from_style(const print_style& style); + print_style to_style(const string& s); string to_json(const context& config, bool pretty_print = false); error load(std::filesystem::path path, context& config); error save(std::filesystem::path path, const context& config); diff --git a/include/error.hpp b/include/error.hpp index 544187c..fa50c58 100644 --- a/include/error.hpp +++ b/include/error.hpp @@ -33,6 +33,8 @@ namespace gdpm::constants::error{ INVALID_KEY, HTTP_RESPONSE_ERR, SQLITE_ERR, + LIBZIP_ERR, + LIBCURL_ERR, JSON_ERR, STD_ERR }; diff --git a/include/http.hpp b/include/http.hpp index dd82ac9..04b2c94 100644 --- a/include/http.hpp +++ b/include/http.hpp @@ -75,8 +75,8 @@ namespace gdpm::http{ LOOP_DETECTED = 508, NOT_EXTENDED = 510, NETWORK_AUTHENTICATION_REQUIRED = 511 - }; + struct response{ long code = 0; string body{}; diff --git a/include/rest_api.hpp b/include/rest_api.hpp index 79a58e8..c18ef2a 100644 --- a/include/rest_api.hpp +++ b/include/rest_api.hpp @@ -44,7 +44,6 @@ namespace gdpm::rest_api{ int type ; int category; int support; - string filter; string user; string godot_version; int max_results; @@ -55,15 +54,15 @@ namespace gdpm::rest_api{ }; request_params make_from_config(const config::context& config); - request_params make_request_params(type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const std::string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); + request_params make_request_params(type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const string& user = GDPM_DEFAULT_ASSET_USER, const string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); string to_json(const rapidjson::Document& doc); string to_string(type_e type); string to_string(support_e support); string to_string(sort_e sort); - void _print_params(const request_params& params); + void _print_params(const request_params& params, const string& filter); rapidjson::Document _parse_json(const string& r, int verbose = 0); - string _prepare_request(const string& url, const request_params& context); + string _prepare_request(const string& url, const request_params& context, const string& filter); bool register_account(const string& username, const string& password, const string& email); bool login(const string& username, const string& password); @@ -71,8 +70,8 @@ namespace gdpm::rest_api{ rapidjson::Document configure(const string& url = constants::HostUrl, type_e type = any, int verbose = 0); rapidjson::Document get_assets_list(const string& url = constants::HostUrl, type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE); - rapidjson::Document get_assets_list(const string& url, const request_params& params = {}); - rapidjson::Document get_asset(const string& url, int asset_id, const request_params& params = {}); + rapidjson::Document get_assets_list(const string& url, const request_params& params = {}, const string& filter = ""); + rapidjson::Document get_asset(const string& url, int asset_id, const request_params& params = {}, const string& filter = ""); bool delete_asset(int asset_id); // ...for moderators bool undelete_asset(int asset_id); // ...for moderators bool set_support_level(int asset_id); // ...for moderators diff --git a/src/config.cpp b/src/config.cpp index 8672905..3174021 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -34,6 +34,18 @@ namespace gdpm::config{ context config; + + string from_style(const print_style& style){ + if(style == print_style::list) return "list"; + else if(style == print_style::table) return "table"; + return "list"; + } + + print_style to_style(const string& s){ + if(s == "list") return print_style::list; + else if(s == "table") return print_style::table; + else return print_style::list; + } string to_json( const context& config, @@ -252,6 +264,8 @@ namespace gdpm::config{ else if(property == "skip-prompt") config.skip_prompt = utils::to_bool(value); else if(property == "enable-file-logging") config.enable_file_logging = utils::to_bool(value); else if(property == "clean-temporary") config.clean_temporary = utils::to_bool(value); + else if(property == "verbosity") config.verbose = std::stoi(value); + else if(property == "style") config.style = to_style(value); else{ return log::error_rc(error( constants::error::INVALID_CONFIG, @@ -271,7 +285,18 @@ namespace gdpm::config{ else if(property == "password") return config.password; else if(property == "path") return config.path; else if(property == "token") return config.token; - else if(property == "package_dir") return config.packages_dir; + else if(property == "package-dir") return config.packages_dir; + else if(property == "tmp-dir") return config.tmp_dir; + else if(property == "remote-sources") return utils::join(config.remote_sources); + else if(property == "jobs") return config.jobs; + else if(property == "timeout") return config.timeout; + else if(property == "sync") return config.enable_sync; + else if(property == "cache") return config.enable_cache; + else if(property == "skip-prompt") return config.skip_prompt; + else if(property == "file-logging") return config.enable_file_logging; + else if(property == "clean-temporary") return config.clean_temporary; + else if(property == "verbosity") return config.verbose; + else if(property == "style") return from_style(config.style); } context make_context( @@ -354,6 +379,7 @@ namespace gdpm::config{ else if(property == "logging") log::println("enable file logging: {}", config.enable_file_logging); else if(property == "clean") log::println("clean temporary files: {}", config.clean_temporary); else if(property == "verbose") log::println("verbose: {}", config.verbose); + else if(property == "style") log::println("style: {}", from_style(config.style)); } @@ -378,6 +404,7 @@ namespace gdpm::config{ else if(property == "logging") table.add_row({"File Logging", std::to_string(config.enable_file_logging)}); else if(property == "clean") table.add_row({"Clean Temporary", std::to_string(config.clean_temporary)}); else if(property == "verbose") table.add_row({"Verbosity", std::to_string(config.verbose)}); + else if(property == "style") table.add_row({"Verbosity", from_style(config.style)}); } void print_properties( @@ -403,6 +430,7 @@ namespace gdpm::config{ _print_property(config, "logging"); _print_property(config, "clean"); _print_property(config, "verbose"); + _print_property(config, "style"); } else { std::for_each( @@ -432,6 +460,7 @@ namespace gdpm::config{ table.add_row({"Logging", std::to_string(config.enable_file_logging)}); table.add_row({"Clean", std::to_string(config.clean_temporary)}); table.add_row({"Verbosity", std::to_string(config.verbose)}); + table.add_row({"Style", from_style(config.style)}); } else{ std::for_each( diff --git a/src/http.cpp b/src/http.cpp index eef2efc..94f90d7 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -15,7 +15,6 @@ namespace gdpm::http{ curl = curl_easy_init(); } - context::~context(){ curl_global_cleanup(); curl_easy_cleanup(curl); diff --git a/src/package.cpp b/src/package.cpp index 654cc89..fde5b0b 100644 --- a/src/package.cpp +++ b/src/package.cpp @@ -132,10 +132,10 @@ namespace gdpm::package{ if(is_missing_data){ doc = rest_api::get_asset(url, p.asset_id, rest_api_params); if(doc.HasParseError() || doc.IsNull()){ - const std::string message = std::format("Error parsing JSON: {}", GetParseError_En(doc.GetParseError())); - log::error(message); - - return error(constants::error::JSON_ERR, std::format("{}: {}", message, GetParseError_En(doc.GetParseError()))); + return log::error_rc(error( + constants::error::JSON_ERR, + std::format("Error parsing JSON: {}", GetParseError_En(doc.GetParseError())) + )); } p.category = doc["category"].GetString(); p.description = doc["description"].GetString(); @@ -149,8 +149,8 @@ namespace gdpm::package{ /* Set directory and temp paths for storage */ package_dir = config.packages_dir + "/" + p.title; - tmp_dir = config.tmp_dir + "/" + p.title; - tmp_zip = tmp_dir + ".zip"; + tmp_dir = config.tmp_dir + "/" + p.title; + tmp_zip = tmp_dir + ".zip"; /* Make directories for packages if they don't exist to keep everything organized */ if(!std::filesystem::exists(config.tmp_dir)) @@ -179,12 +179,10 @@ namespace gdpm::package{ if(response.code == http::OK){ log::println("Done."); }else{ - error error( + return log::error_rc(error( constants::error::HTTP_RESPONSE_ERR, std::format("HTTP Error: {}", response.code) - ); - log::error(error); - return error; + )); } } @@ -195,7 +193,13 @@ namespace gdpm::package{ /* Extract all the downloaded packages to their appropriate directory location. */ for(const auto& p : dir_pairs){ - int error_code = utils::extract_zip(p.first.c_str(), p.second.c_str()); + int ec = utils::extract_zip(p.first.c_str(), p.second.c_str()); + if(ec){ + log::error_rc(error( + constants::error::LIBZIP_ERR, + std::format("libzip returned an error code {}", ec) + )); + } } /* Update the cache data with information from */ @@ -359,8 +363,7 @@ namespace gdpm::package{ /* If no package titles provided, update everything and then exit */ rest_api::request_params rest_api_params = rest_api::make_from_config(config); if(package_titles.empty()){ - std::string url{constants::HostUrl}; - url += rest_api::endpoints::GET_AssetId; + string url{constants::HostUrl + rest_api::endpoints::GET_AssetId}; Document doc = rest_api::get_assets_list(url, rest_api_params); if(doc.IsNull()){ constexpr const char *message = "Could not get response from server. Aborting."; @@ -371,7 +374,7 @@ namespace gdpm::package{ } /* Fetch remote asset data and compare to see if there are package updates */ - std::vector p_updates = {}; + package::title_list p_updates = {}; result_t r_cache = cache::get_package_info_by_title(package_titles); package::info_list p_cache = r_cache.unwrap_unsafe(); @@ -382,10 +385,9 @@ namespace gdpm::package{ /* Check version information to see if packages need updates */ for(const auto& p : p_cache){ - std::string url{constants::HostUrl}; - url += rest_api::endpoints::GET_AssetId; + string url{constants::HostUrl + rest_api::endpoints::GET_AssetId}; Document doc = rest_api::get_asset(url, p.asset_id); - std::string remote_version = doc["version"].GetString(); + string remote_version = doc["version"].GetString(); if(p.version != remote_version){ p_updates.emplace_back(p.title); } @@ -412,7 +414,6 @@ namespace gdpm::package{ ){ result_t r_cache = cache::get_package_info_by_title(package_titles); info_list p_cache = r_cache.unwrap_unsafe(); - http::context http; if(!p_cache.empty() && !config.enable_sync){ print_list(p_cache); @@ -422,20 +423,14 @@ namespace gdpm::package{ rest_api::request_params rest_api_params = rest_api::make_from_config(config); for(const auto& p_title : package_titles){ using namespace rapidjson; - - rest_api_params.filter = http.url_escape(p_title); - - string request_url{constants::HostUrl}; - request_url += rest_api::endpoints::GET_Asset; - Document doc = rest_api::get_assets_list(request_url, rest_api_params); + string request_url{constants::HostUrl + rest_api::endpoints::GET_Asset}; + Document doc = rest_api::get_assets_list(request_url, rest_api_params, p_title); if(doc.IsNull()){ return log::error_rc(error( constants::error::HOST_UNREACHABLE, - "Could not fetch metadata." + "Could not fetch metadata. Aborting." )); } - - // log::info("{} package(s) found...", doc["total_items"].GetInt()); print_list(doc); } return error(); diff --git a/src/package_manager.cpp b/src/package_manager.cpp index eff5d50..426f584 100644 --- a/src/package_manager.cpp +++ b/src/package_manager.cpp @@ -21,7 +21,6 @@ #include #include #include -#include "clipp.h" #include "argparse/argparse.hpp" #include @@ -43,13 +42,7 @@ namespace gdpm::package_manager{ config::context config; action_e action; - // opts_t opts; - bool skip_prompt = false; - bool clean_tmp_dir = false; - int priority = -1; - error initialize(int argc, char **argv){ - // curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); config = config::make_context(); action = action_e::none; @@ -60,15 +53,13 @@ namespace gdpm::package_manager{ } error error = config::load(config.path, config); if(error.has_occurred()){ - log::error(error); - return error; + return log::error_rc(error); } /* Create the local databases if it doesn't exist already */ error = cache::create_package_database(); if(error.has_occurred()){ - log::error(error); - return error; + return log::error_rc(error); } return error; @@ -81,6 +72,7 @@ namespace gdpm::package_manager{ return error; } + template auto set_if_used( const argparse::ArgumentParser& cmd, @@ -93,9 +85,10 @@ namespace gdpm::package_manager{ } }; + string_list get_values_from_parser( const argparse::ArgumentParser& cmd, - const std::string& arg = "packages" + const string& arg = "packages" ){ if(cmd.is_used(arg)) return cmd.get(arg); @@ -104,7 +97,6 @@ namespace gdpm::package_manager{ error parse_arguments(int argc, char **argv){ - using namespace clipp; using namespace argparse; /* Replace cxxopts with clipp */ @@ -140,10 +132,11 @@ namespace gdpm::package_manager{ program.add_description("Manage Godot engine assets from CLI"); program.add_argument("-v", "--verbose") .action([&](const auto&){ config.verbose += 1; }) + .append() .default_value(false) .implicit_value(true) .help("set verbosity level") - .nargs(0); + .nargs(nargs_pattern::optional); install_command.add_description("install package(s)"); install_command.add_argument("packages") @@ -322,7 +315,6 @@ namespace gdpm::package_manager{ .nargs(1) .default_value("list"); - config_set.add_description("set config property"); config_set.add_argument("property") .help("property name") diff --git a/src/rest_api.cpp b/src/rest_api.cpp index fbf56e3..c6a66d9 100644 --- a/src/rest_api.cpp +++ b/src/rest_api.cpp @@ -14,6 +14,7 @@ #include #include #include + namespace gdpm::rest_api{ request_params make_from_config(const config::context& config){ @@ -26,7 +27,6 @@ namespace gdpm::rest_api{ type_e type, int category, support_e support, - const string& filter, const string& user, const string& godot_version, int max_results, @@ -39,7 +39,6 @@ namespace gdpm::rest_api{ .type = type, .category = category, .support = support, - .filter = filter, .user = user, .godot_version = godot_version, .max_results = max_results, @@ -81,7 +80,7 @@ namespace gdpm::rest_api{ PrettyWriter writer(buffer); d.Accept(writer); - if(verbose > 1) + if(verbose > log::INFO) log::info("JSON Response: \n{}", buffer.GetString()); return d; } @@ -112,7 +111,6 @@ namespace gdpm::rest_api{ } string to_string(sort_e sort){ - string _s{"sort="}; switch(sort){ case none: _s += ""; break; @@ -125,15 +123,16 @@ namespace gdpm::rest_api{ } string _prepare_request( - const string &url, - const request_params &c + const string& url, + const request_params &c, + const string& filter ){ string request_url{url}; request_url += to_string(static_cast(c.type)); request_url += (c.category <= 0) ? "&category=" : "&category="+std::to_string(c.category); request_url += "&" + to_string(static_cast(c.support)); request_url += "&" + to_string(static_cast(c.sort)); - request_url += (!c.filter.empty()) ? "&filter="+c.filter : ""; + request_url += (!filter.empty()) ? "&filter="+filter : ""; request_url += (!c.godot_version.empty()) ? "&godot_version="+c.godot_version : ""; request_url += "&max_results=" + std::to_string(c.max_results); request_url += "&page=" + std::to_string(c.page); @@ -141,7 +140,7 @@ namespace gdpm::rest_api{ return request_url; } - void _print_params(const request_params& params){ + void _print_params(const request_params& params, const string& filter){ log::println("params: \n" "\ttype: {}\n" "\tcategory: {}\n" @@ -152,7 +151,7 @@ namespace gdpm::rest_api{ params.type, params.category, params.support, - params.filter, + filter, params.godot_version, params.max_results ); @@ -190,7 +189,6 @@ namespace gdpm::rest_api{ .type = type, .category = category, .support = support, - .filter = filter, .user = user, .godot_version = godot_version, .max_results = max_results, @@ -204,35 +202,37 @@ namespace gdpm::rest_api{ rapidjson::Document get_assets_list( const string& url, - const request_params& c + const request_params& c, + const string& filter ){ http::context http; http::request_params http_params; - http_params.headers.insert(http::header("Accept", "*/*")); - http_params.headers.insert(http::header("Accept-Encoding", "application/gzip")); - http_params.headers.insert(http::header("Content-Encoding", "application/gzip")); - http_params.headers.insert(http::header("Connection", "keep-alive")); - string request_url = _prepare_request(url, c); + // http_params.headers.insert(http::header("Accept", "*/*")); + // http_params.headers.insert(http::header("Accept-Encoding", "application/gzip")); + // http_params.headers.insert(http::header("Content-Encoding", "application/gzip")); + // http_params.headers.insert(http::header("Connection", "keep-alive")); + string request_url = _prepare_request(url, c, http.url_escape(filter)); http::response r = http.request_get(request_url, http_params); if(c.verbose >= log::INFO) log::info("get_asset().URL: {}", request_url); return _parse_json(r.body, c.verbose); } + rapidjson::Document get_asset( const string& url, int asset_id, - const rest_api::request_params& api_params + const rest_api::request_params& api_params, + const string& filter ){ /* Set up HTTP request */ http::context http; http::request_params http_params; - http_params.headers.insert(http::header("Accept", "*/*")); - http_params.headers.insert(http::header("Accept-Encoding", "application/gzip")); - http_params.headers.insert(http::header("Content-Encoding", "application/gzip")); - http_params.headers.insert(http::header("Connection", "keep-alive")); - - string request_url = utils::replace_all(_prepare_request(url, api_params), "{id}", std::to_string(asset_id)); + // http_params.headers.insert(http::header("Accept", "*/*")); + // http_params.headers.insert(http::header("Accept-Encoding", "application/gzip")); + // http_params.headers.insert(http::header("Content-Encoding", "application/gzip")); + // http_params.headers.insert(http::header("Connection", "keep-alive")); + string request_url = utils::replace_all(_prepare_request(url, api_params, http.url_escape(filter)), "{id}", std::to_string(asset_id)); http::response r = http.request_get(request_url.c_str(), http_params); if(api_params.verbose >= log::INFO) log::info("get_asset().URL: {}", request_url); @@ -240,14 +240,17 @@ namespace gdpm::rest_api{ return _parse_json(r.body); } + bool delete_asset(int asset_id){ return false; } + bool undelete_asset(int asset_id){ return false; } + bool set_support_level(int asset_id){ return false; }