Implemented parallel downloads through CURL multi interface and added purge command

- Update `README.md` file with examples
- Fixed error messages showing no or wrong message
- Changed the prompt message when installing, removing, etc.
- Changed how http::request works
- Added `http::multi` class for parallel downloads
- Removed separate `concepts.hpp` file
TODO: Fix ZIP not extracting after running the `install` command
This commit is contained in:
David Allen 2023-07-10 20:26:15 -06:00
parent 766eabd5b2
commit 807aa8e5b2
21 changed files with 1158 additions and 758 deletions

View file

@ -1,6 +0,0 @@
#pragma once
#include <string>
namespace gdpm::concepts{
template <typename...Args> concept RequireMinArgs = requires (std::size_t min){ sizeof...(Args) > min; };
}

View file

@ -36,8 +36,8 @@ namespace gdpm::print{
#define GDPM_CONFIG_REMOTE_SOURCES std::pair<std::string, std::string>(constants::RemoteName, constants::HostUrl)
#define GDPM_CONFIG_THREADS 1
#define GDPM_CONFIG_TIMEOUT_MS 30000
#define GDPM_CONFIG_ENABLE_SYNC 1
#define GDPM_CONFIG_ENABLE_FILE_LOGGING 0
#define GDPM_CONFIG_ENABLE_SYNC true
#define GDPM_CONFIG_ENABLE_FILE_LOGGING true
#define GDPM_CONFIG_VERBOSE 0
/* Defines the default package cache for local storage */

View file

@ -2,6 +2,7 @@
#include "log.hpp"
#include "types.hpp"
#include "utils.hpp"
#include <fmt/core.h>
#include <new>
#include <string>
@ -15,11 +16,14 @@ namespace gdpm::constants::error{
UNKNOWN_COMMAND,
UNKNOWN_ARGUMENT,
ARGPARSE_ERROR,
ASSERTION_FAILED,
PRECONDITION_FAILED,
POSTCONDITION_FAILED,
NOT_FOUND,
NOT_DEFINED,
NOT_IMPLEMENTED,
NO_PACKAGE_FOUND,
PATH_NOT_DEFINED,
MALFORMED_PATH,
FILE_EXISTS,
FILE_NOT_FOUND,
DIRECTORY_EXISTS,
@ -43,12 +47,17 @@ namespace gdpm::constants::error{
"",
"An unknown error has occurred.",
"Unknown command.",
"Unknown argument.",
"Could not parse argument.",
"Assertion condition failed.",
"Pre-condition failed.",
"Post-condition failed.",
"Resource not found.",
"Function not defined.",
"Function not implemented.",
"Resource not defined.",
"Resource not implemented.",
"No package found.",
"Path is not well-defined",
"File found.",
"Path is malformed.",
"File already exists",
"File does not exist.",
"Directory exists.",
"Directory not found.",
@ -58,6 +67,10 @@ namespace gdpm::constants::error{
"Invalid configuration.",
"Invalid key.",
"An HTTP response error has occurred.",
"A SQLite error has occurred.",
"A libzip error has occurred.",
"A libcurl error has occurred.",
"A JSON error has occurred.",
"An error has occurred."
};
@ -73,10 +86,12 @@ namespace gdpm::constants::error{
};
namespace gdpm{
namespace ec = constants::error;
class error {
public:
constexpr explicit error(int code = 0, const string& message = "{code}"):
m_code(code), m_message(message == "{code}" ? constants::error::get_message(code): message)
constexpr explicit error(int code = 0, const string& message = "{default}"):
m_code(code),
m_message(utils::replace_all(message, "{default}", ec::get_message(code)))
{}
void set_code(int code) { m_code = code; }
@ -106,13 +121,21 @@ namespace gdpm{
#endif
}
// static constexpr void error(int code, const string& message = "{default}"){
// log::error(gdpm::error(code, message));
// }
static constexpr gdpm::error error_rc(const gdpm::error& e){
error(e);
log::error(e);
return e;
}
static void error(const char *p, const gdpm::error& e){
println("{}{}{}", p, prefix.contents, e.get_message());
static constexpr gdpm::error error_rc(int code, const string& message = "{default}"){
return error_rc(gdpm::error(code, message));
}
}
namespace concepts{
template <typename T>concept error_t = requires{ std::is_same<error, T>::value; };
}
}

View file

@ -2,6 +2,12 @@
#include "constants.hpp"
#include "types.hpp"
#include "indicators/indeterminate_progress_bar.hpp"
#include "indicators/dynamic_progress.hpp"
#include "indicators/progress_bar.hpp"
#include "indicators/block_progress_bar.hpp"
#include "utils.hpp"
#include <memory>
#include <unordered_map>
#include <curl/curl.h>
#include <curl/easy.h>
@ -10,6 +16,13 @@ namespace gdpm::http{
using headers_t = std::unordered_map<string, string>;
using header = std::pair<string, string>;
enum method{
GET,
POST,
PUT,
DELETE
};
// REF: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
enum response_code{
CONTINUE = 100,
@ -77,6 +90,11 @@ namespace gdpm::http{
NETWORK_AUTHENTICATION_REQUIRED = 511
};
enum transfer_type{
REQUEST,
DOWNLOAD
};
struct response{
long code = 0;
string body{};
@ -85,31 +103,91 @@ namespace gdpm::http{
};
struct request_params {
struct request {
headers_t headers = {};
method method = method::GET;
size_t timeout = GDPM_CONFIG_TIMEOUT_MS;
int verbose = 0;
};
using namespace indicators;
// BlockProgressBar bar {
// option::BarWidth{50},
// // option::Start{"["},
// // option::Fill{"="},
// // option::Lead{">"},
// // option::Remainder{" "},
// // option::End{"]"},
// option::PrefixText{"Downloading file "},
// option::PostfixText{""},
// option::ForegroundColor{Color::green},
// option::FontStyles{std::vector<FontStyle>{FontStyle::bold}},
// };
// // option::ShowElapsedTime{true},
// // option::ShowRemainingTime{true},
// IndeterminateProgressBar bar_unknown {
// option::BarWidth{50},
// option::Start{"["},
// option::Fill{"."},
// option::Lead{"<==>"},
// option::PrefixText{"Downloading file "},
// option::End{"]"},
// option::PostfixText{""},
// option::ForegroundColor{Color::green},
// option::FontStyles{std::vector<FontStyle>{FontStyle::bold}},
// };
struct transfer : public non_copyable{
transfer(){ curl = curl_easy_init(); }
transfer(transfer&&){}
~transfer(){ }
CURLcode res;
int id;
CURL *curl = nullptr;
FILE *fp = nullptr;
utils::memory_buffer data = {0};
};
using transfers = std::vector<transfer>;
using responses = std::vector<response>;
class context : public non_copyable{
public:
context();
~context();
inline CURL* const get_curl() const;
string url_escape(const string& url);
response request_get(const string& url, const http::request_params& params = http::request_params());
response request_post(const string& url, const http::request_params& params = http::request_params());
response download_file(const string& url, const string& storage_path, const http::request_params& params = http::request_params());
response request(const string& url, const http::request& params = http::request());
response download_file(const string& url, const string& storage_path, const http::request& params = http::request());
long get_download_size(const string& url);
long get_bytes_downloaded(const string& url);
private:
CURL *curl;
curl_slist* _add_headers(CURL *curl, const headers_t& headers);
CURL *curl = nullptr;
};
extern context http;
class multi{
public:
multi(long max_allowed_transfers = 2);
~multi();
string url_escape(const string& url);
ptr<transfers> make_requests(const string_list& urls, const http::request& params = http::request());
ptr<transfers> make_downloads(const string_list& url, const string_list& storage_path, const http::request& params = http::request());
ptr<responses> execute(ptr<transfers> transfers, size_t timeout = 1000);
private:
DynamicProgress<BlockProgressBar> progress_bars;
CURLM *cm = nullptr;
CURLMsg *cmessage = nullptr;
CURLMcode cres;
int messages_left = -1;
};
curl_slist* add_headers(CURL *curl, const headers_t& headers);
static size_t write_to_buffer(char *contents, size_t size, size_t nmemb, void *userdata);
static size_t write_to_stream(char *ptr, size_t size, size_t nmemb, void *userdata);
static int show_download_progress(void *ptr, curl_off_t total_download, curl_off_t current_downloaded, curl_off_t total_upload, curl_off_t current_upload);
}

View file

@ -110,8 +110,11 @@ namespace gdpm::package {
GDPM_DLL_EXPORT error search(const config::context& config, const title_list& package_titles, const params& params = package::params());
GDPM_DLL_EXPORT error list(const config::context& config, const params& params = package::params());
GDPM_DLL_EXPORT error export_to(const path_list& paths);
GDPM_DLL_EXPORT error clean(const config::context& config, const title_list& package_titles);
GDPM_DLL_EXPORT error purge(const config::context& config);
GDPM_DLL_EXPORT error link(const config::context& config, const title_list& package_titles, const params& params = package::params());
GDPM_DLL_EXPORT error clone(const config::context& config, const title_list& package_titles, const params& params = package::params());
GDPM_DLL_EXPORT result_t<info_list> fetch(const config::context& config, const title_list& package_titles);
GDPM_DLL_EXPORT void print_list(const rapidjson::Document& json);
@ -120,12 +123,10 @@ namespace gdpm::package {
GDPM_DLL_EXPORT void print_table(const rapidjson::Document& json);
GDPM_DLL_EXPORT result_t<info_list> get_package_info(const opts_t& opts);
GDPM_DLL_EXPORT result_t<title_list> get_package_titles(const info_list& packages);
GDPM_DLL_EXPORT void clean_temporary(const config::context& config, const title_list& package_titles);
GDPM_DLL_EXPORT void read_file_inputs(title_list& package_titles, const path_list& paths);
GDPM_DLL_EXPORT info_list find_cached_packages(const title_list& package_titles);
GDPM_DLL_EXPORT info_list find_installed_packages(const title_list& package_titles);
/* Dependency Management API */
GDPM_DLL_EXPORT result_t<info_list> synchronize_database(const config::context& config, const title_list& package_titles);
GDPM_DLL_EXPORT result_t<info_list> resolve_dependencies(const config::context& config, const title_list& package_titles);
GDPM_DLL_EXPORT string to_json(const info& info, bool pretty_print = false);

View file

@ -27,6 +27,7 @@ namespace gdpm::package_manager {
update,
search,
p_export, /* reserved keyword */
purge,
list,
link,
clone,

View file

@ -63,25 +63,29 @@ namespace gdpm::rest_api{
};
request_params make_from_config(const config::context& config);
string to_json(const rapidjson::Document& doc);
string to_json(const json::document& doc);
string to_string(type_e type);
string to_string(support_e support);
string to_string(sort_e sort);
error print_params(const request_params& params, const string& filter = "");
error print_asset(const request_params& params, const string& filter = "", const print::style& style = print::style::list);
rapidjson::Document _parse_json(const string& r, int verbose = 0);
json::document _parse_json(const string& r, int verbose = 0);
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);
bool logout();
rapidjson::Document configure(const string& url = constants::HostUrl, type_e type = any, int verbose = 0);
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 = "");
json::document configure(const string& url = constants::HostUrl, type_e type = any, int verbose = 0);
json::document get_assets_list(const string& url, const request_params& params = {}, const string& filter = "");
json::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
namespace multi{
json::documents get_assets(const string_list& urls, id_list aset_ids, const request_params& api_params, const string_list& filters);
}
/*
POST /asset

View file

@ -2,13 +2,12 @@
#include "log.hpp"
#include "error.hpp"
#include "types.hpp"
#include <functional>
#include <type_traits>
namespace gdpm{
template <class T, error_t U = error>
template <class T, concepts::error_t U = error>
class result_t {
public:
result_t() = delete;
@ -35,13 +34,14 @@ namespace gdpm{
fn_error = error;
}
constexpr U get_error() const{
return std::get<U>(data);
}
constexpr std::unique_ptr<T> unwrap() const {
/* First, check if ok() and error() are defined. */
if(!fn_error || !fn_ok){
error error(
constants::error::NOT_DEFINED
);
log::error(error);
log::error(error(ec::NOT_DEFINED));
return nullptr;
}
/* Then, attempt unwrap the data. */

View file

@ -1,5 +1,6 @@
#pragma once
#include <rapidjson/document.h>
#include <tuple>
#include <functional>
#include <type_traits>
@ -41,13 +42,11 @@ namespace gdpm{
SIZE_T = 6,
};
template <typename T>
concept error_t = requires{ std::is_same<error, T>::value; };
using string = std::string;
using string_list = std::vector<string>;
using string_map = std::unordered_map<string, string>;
using string_pair = std::pair<string, string>;
using id_list = std::vector<int>;
using any = std::any;
using var = std::variant<int, float, bool, string, string_list, string_map, size_t>;
template <typename T = var>
@ -65,6 +64,15 @@ namespace gdpm{
template <typename T = error>
using _task_list = std::vector<std::future<T>>;
using task_list = _task_list<error>;
template <typename T>
using ptr = std::unique_ptr<T>;
namespace json{
using document = rapidjson::Document;
using documents = std::vector<rapidjson::Document>;
}
namespace concepts{
template <typename...Args> concept require_min_args = requires (std::size_t min){ sizeof...(Args) > min; };
}
inline string_list unwrap(const var_args& args){
string_list sl;

View file

@ -1,6 +1,7 @@
#pragma once
#include "constants.hpp"
#include "types.hpp"
#include <algorithm>
#include <cstddef>
@ -71,6 +72,11 @@ namespace gdpm::utils {
std::move(part, from.end(), std::back_inserter(from));
from.erase(part);
}
template <class T>
std::vector<T> append(const std::vector<T>& a, const std::vector<T>& b){
a.insert(std::end(a), std::begin(b), std::end(b));
return a;
}
bool to_bool(const std::string& s);
std::vector<std::string> split_lines(const std::string& contents);
@ -84,7 +90,7 @@ namespace gdpm::utils {
std::vector<std::string> parse_lines(const std::string& s);
std::string replace_first(const std::string& s, const std::string& from, const std::string& to);
std::string replace_all(const std::string& s, const std::string& from, const std::string& to);
int extract_zip(const char *archive, const char *dest, int verbose = 0);
error extract_zip(const char *archive, const char *dest, int verbose = 0);
std::string prompt_user(const char *message);
bool prompt_user_yn(const char *message);
void delay(std::chrono::milliseconds milliseconds = GDPM_REQUEST_DELAY);
@ -93,11 +99,6 @@ namespace gdpm::utils {
std::string convert_size(long size);
// TODO: Add function to get size of decompressed zip
namespace curl {
extern size_t write_to_buffer(char *contents, size_t size, size_t nmemb, void *userdata);
extern size_t write_to_stream(char *ptr, size_t size, size_t nmemb, void *userdata);
extern int show_progress(void *ptr, curl_off_t total_download, curl_off_t current_downloaded, curl_off_t total_upload, curl_off_t current_upload);
}
namespace json {
std::string from_array(const std::set<std::string>& a, const std::string& prefix);
std::string from_object(const std::unordered_map<std::string, std::string>& m, const std::string& prefix, const std::string& spaces);