Initial commit

First commit with most of the main features implemented. May still need
some bug fixes here and there.
This commit is contained in:
David Allen 2021-12-30 12:56:37 -06:00
commit 1893c7c36b
26 changed files with 2839 additions and 0 deletions

25
include/cache.hpp Normal file
View file

@ -0,0 +1,25 @@
#include <sqlite3.h>
#include <vector>
#include <string>
namespace gdpm::package_manager{
struct package_info;
}
namespace gdpm::cache{
using namespace package_manager;
int create_package_database();
int insert_package_info(const std::vector<package_info>& package);
std::vector<package_info> get_package_info_by_id(const std::vector<size_t>& package_ids);
std::vector<package_info> get_package_info_by_title(const std::vector<std::string>& package_titles);
std::vector<package_info> get_installed_packages();
int update_package_info(const std::vector<package_info>& packages);
int update_sync_info(const std::vector<std::string>& download_urls);
int delete_packages(const std::vector<std::string>& package_titles);
int delete_packages(const std::vector<size_t>& package_ids);
int drop_package_database();
std::string to_values(const package_info& package);
std::string to_values(const std::vector<package_info>& packages);
}

45
include/colors.hpp Normal file
View file

@ -0,0 +1,45 @@
#if GDPM_ENABLE_COLORS == 1
#define GDPM_COLOR_BLACK "\033[0;30m"
#define GDPM_COLOR_BLUE "\033[0;34m"
#define GDPM_COLOR_GREEN "\033[0;32m"
#define GDPM_COLOR_CYAN "\033[0;36m"
#define GDPM_COLOR_RED "\033[0;31m"
#define GDPM_COLOR_PURPLE "\033[0;35m"
#define GDPM_COLOR_BROWN "\033[0;33m"
#define GDPM_COLOR_GRAY "\033[0;37m"
#define GDPM_COLOR_DARK_GRAY "\033[0;30m"
#define GDPM_COLOR_LIGHT_BLUE "\033[0;34m"
#define GDPM_COLOR_LIGHT_GREEN "\033[0;32m"
#define GDPM_COLOR_LIGHT_CYAN "\033[0;36m"
#define GDPM_COLOR_LIGHT_RED "\033[0;31m"
#define GDPM_COLOR_LIGHT_PURPLE "\033[0;35m"
#define GDPM_COLOR_YELLOW "\033[0;33m"
#define GDPM_COLOR_WHITE "\033[0;37m"
#else
#define GDPM_COLOR_BLACK
#define GDPM_COLOR_BLUE
#define GDPM_COLOR_GREEN
#define GDPM_COLOR_CYAN
#define GDPM_COLOR_RED
#define GDPM_COLOR_PURPLE
#define GDPM_COLOR_BROWN
#define GDPM_COLOR_GRAY
#define GDPM_COLOR_DARK_GRAY
#define GDPM_COLOR_LIGHT_BLUE
#define GDPM_COLOR_LIGHT_GREEN
#define GDPM_COLOR_LIGHT_CYAN
#define GDPM_COLOR_LIGHT_RED
#define GDPM_COLOR_LIGHT_PURPLE
#define GDPM_COLOR_YELLOW
#define GDPM_COLOR_WHITE
#endif
#define GDPM_COLOR_LOG_RESET GDPM_COLOR_WHITE
#define GDPM_COLOR_LOG_INFO GDPM_COLOR_LOG_RESET
#define GDPM_COLOR_LOG_ERROR GDPM_COLOR_RED
#define GDPM_COLOR_LOG_DEBUG GDPM_COLOR_YELLOW
#define GDPM_COLOR_LOG_WARNING GDPM_COLOR_YELLOW

32
include/config.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include "constants.hpp"
#include <string>
#include <filesystem>
#include <vector>
namespace gdpm::config{
struct config_context{
std::string username;
std::string password;
std::string path;
std::string token;
std::string godot_version;
std::string packages_dir;
std::string tmp_dir;
std::vector<std::string> remote_sources;
size_t threads;
size_t timeout;
bool enable_sync;
bool enable_file_logging;
int verbose;
};
std::string to_json(const config_context& params);
config_context load(std::filesystem::path path, int verbose = 0);
int save(const config_context& config, int verbose = 0);
config_context make_config(const std::string& username = GDPM_CONFIG_USERNAME, const std::string& password = GDPM_CONFIG_PASSWORD, const std::string& path = GDPM_CONFIG_PATH, const std::string& token = GDPM_CONFIG_TOKEN, const std::string& godot_version = GDPM_CONFIG_GODOT_VERSION, const std::string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const std::string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const std::vector<std::string>& remote_sources = {GDPM_CONFIG_REMOTE_SOURCES}, size_t threads = GDPM_CONFIG_THREADS, size_t timeout = 0, bool enable_sync = GDPM_CONFIG_ENABLE_SYNC, bool enable_file_logging = GDPM_CONFIG_ENABLE_FILE_LOGGING, int verbose = GDPM_CONFIG_VERBOSE);
extern config_context config;
}

74
include/constants.hpp Normal file
View file

@ -0,0 +1,74 @@
#pragma once
#include <string>
namespace gdpm::constants{
constexpr const char *ConfigPath = "config.ini";
constexpr const char *LocalPackagesDir = "$HOME/.config/gdpm";
constexpr const char *UserAgent = "libcurl-agent/1.0";
constexpr const char *AssetRepo = "https://godotengine.org/asset-library/api/asset";
constexpr const char *HostUrl = "https://godotengine.org/asset-library/api";
constexpr const char *LockfilePath = "$HOME/.config/gdpm/gdpm.lck";
constexpr const char *TmpPath = "$HOME/.config/gdpm/tmp";
}
/* Defines to set when building with -DGPM_* */
#define GDPM_CONFIG_USERNAME ""
#define GDPM_CONFIG_PASSWORD ""
#define GDPM_CONFIG_PATH "config.json"
#define GDPM_CONFIG_TOKEN ""
#define GDPM_CONFIG_GODOT_VERSION "3.4"
#define GDPM_CONFIG_LOCAL_PACKAGES_DIR "tests/gdpm/packages"
#define GDPM_CONFIG_LOCAL_TMP_DIR "tests/gdpm/.tmp"
#define GDPM_CONFIG_REMOTE_SOURCES 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_VERBOSE 0
/* Defines package cache for local storage */
#define GDPM_PACKAGE_CACHE_ENABLE 1
#define GDPM_PACKAGE_CACHE_PATH "tests/gdpm/packages.db"
#define GDPM_PACKAGE_CACHE_TABLENAME "cache"
#define GDPM_PACKAGE_CACHE_COLNAMES "asset_id, type, title, author, author_id, version, godot_version, cost, description, modify_date, support_level, category, remote_source, download_url, download_hash, is_installed, install_path"
/* Defines to set default assets API params */
#define GDPM_DEFAULT_ASSET_TYPE any
#define GDPM_DEFAULT_ASSET_CATEGORY 0
#define GDPM_DEFAULT_ASSET_SUPPORT all
#define GDPM_DEFAULT_ASSET_FILTER ""
#define GDPM_DEFAULT_ASSET_USER ""
#define GDPM_DEFAULT_ASSET_GODOT_VERSION ""
#define GDPM_DEFAULT_ASSET_MAX_RESULTS 500
#define GDPM_DEFAULT_ASSET_PAGE 0
#define GDPM_DEFAULT_ASSET_SORT none
#define GDPM_DEFAULT_ASSET_REVERSE false
#define GDPM_DEFAULT_ASSET_VERBOSE 0
/* Define misc. macros */
#if defined(_WIN32)
#define GDPM_DLL_EXPORT __declspec(dllexport)
#define GDPM_DLL_IMPORT __declspec(dllimport)
#else
#define GDPM_DLL_EXPORT
#define GDPM_DLL_IMPORT
#endif
#define GDPM_READFILE_IMPL 1
#define GDPM_DELAY_HTTP_REQUESTS 1
#ifndef GDPM_REQUEST_DELAY
#define GDPM_REQUEST_DELAY 200ms
#endif
#ifndef GDPM_ENABLE_COLORS
#define GDPM_ENABLE_COLORS 1
#endif
#ifndef GDPM_LOG_LEVEL
#define GDPM_LOG_LEVEL 1
#endif
#ifndef GDPM_ENABLE_TIMESTAMPS
#define GDPM_ENABLE_TIMESTAMPS 1
#endif

18
include/http.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "constants.hpp"
#include <unordered_map>
namespace gdpm::http{
struct response{
long code = 0;
std::string body{};
std::unordered_map<std::string, std::string> headers{};
};
response request_get(const std::string& url, size_t timeout = GDPM_CONFIG_TIMEOUT_MS);
response request_post(const std::string& url, const char *post_fields="", size_t timeout = GDPM_CONFIG_TIMEOUT_MS);
response download_file(const std::string& url, const std::string& storage_path, size_t timeout = GDPM_CONFIG_TIMEOUT_MS);
}

81
include/log.hpp Normal file
View file

@ -0,0 +1,81 @@
#pragma once
#include "utils.hpp"
#include "colors.hpp"
#include <fmt/core.h>
#if __cplusplus > 201703L
// #include <format>
#else
#endif
#include <fmt/printf.h>
#include <fmt/format.h>
namespace gdpm::log
{
template <typename...Args> concept RequireMinArgs = requires (std::size_t min){ sizeof...(Args) > min; };
static void vlog(fmt::string_view format, fmt::format_args args){
fmt::vprint(format, args);
}
static void vlog(FILE *fp, fmt::string_view format, fmt::format_args args){
fmt::vprint(fp, format, args);
}
template <typename S, typename...Args>
static constexpr void info(const S& format, Args&&...args){
#if GDPM_LOG_LEVEL > 0
vlog(
fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...)
);
#endif
}
template <typename S, typename...Args>
static constexpr void info_n(const S& format, Args&&...args){
vlog(
fmt::format(GDPM_COLOR_LOG_INFO "[INFO {}] {}" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...)
);
}
template <typename S, typename...Args>
static constexpr void error(const S& format, Args&&...args){
#if GDPM_LOG_LEVEL > 1
vlog(
fmt::format(GDPM_COLOR_LOG_ERROR "[ERROR {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...)
);
#endif
}
template <typename S, typename...Args>
static constexpr void debug(const S& format, Args&&...args){
#if GDPM_LOG_LEVEL > 1
vlog(
fmt::format(GDPM_COLOR_LOG_DEBUG "[DEBUG {}] {}\n" GDPM_COLOR_LOG_RESET, utils::timestamp(), format),
fmt::make_args_checked<Args...>(format, args...)
);
#endif
}
template <typename S, typename...Args>
static constexpr void print(const S& format, Args&&...args){
vlog(
fmt::format("{}", format),
fmt::make_args_checked<Args...>(format, args...)
);
}
template <typename S, typename...Args>
static constexpr void println(const S& format, Args&&...args){
vlog(
fmt::format("{}\n", format),
fmt::make_args_checked<Args...>(format, args...)
);
}
}

View file

@ -0,0 +1,83 @@
#pragma once
#include "config.hpp"
#include <cstdio>
#include <cxxopts.hpp>
#include <memory>
#include <string>
#include <vector>
#include <rapidjson/document.h>
#include <curl/curl.h>
namespace gdpm::package_manager{
extern std::vector<std::string> repo_sources;
extern CURL *curl;
extern CURLcode res;
extern config::config_context config;
struct package_info{
size_t asset_id;
std::string type;
std::string title;
std::string author;
size_t author_id;
std::string version;
std::string godot_version;
std::string cost;
std::string description;
std::string modify_date;
std::string support_level;
std::string category;
std::string remote_source;
std::string download_url;
std::string download_hash;
bool is_installed;
std::string install_path;
};
struct cxxargs{
cxxopts::ParseResult result;
cxxopts::Options options;
};
enum command_e{
install,
remove,
update,
search,
list,
link,
clone,
clean,
sync,
add_remote,
delete_remote,
help,
none
};
GDPM_DLL_EXPORT int initialize(int argc, char **argv);
GDPM_DLL_EXPORT int execute();
GDPM_DLL_EXPORT void finalize();
GDPM_DLL_EXPORT void install_packages(const std::vector<std::string>& package_titles);
GDPM_DLL_EXPORT void remove_packages(const std::vector<std::string>& package_titles);
GDPM_DLL_EXPORT void update_packages(const std::vector<std::string>& package_titles);
GDPM_DLL_EXPORT void search_for_packages(const std::vector<std::string>& package_titles);
GDPM_DLL_EXPORT void list_installed_packages();
GDPM_DLL_EXPORT void read_package_contents(const std::string& package_title);
GDPM_DLL_EXPORT void clean_temporary(const std::vector<std::string>& package_titles);
GDPM_DLL_EXPORT void link_packages(const std::vector<std::string>& package_titles, const std::vector<std::string>& paths);
GDPM_DLL_EXPORT void clone_packages(const std::vector<std::string>& package_titles, const std::vector<std::string>& paths);
GDPM_DLL_EXPORT void add_remote_repository(const std::string& repository, ssize_t offset = -1);
GDPM_DLL_EXPORT void delete_remote_repository(const std::string& repository);
GDPM_DLL_EXPORT void delete_remote_repository(ssize_t index);
GDPM_DLL_EXPORT cxxargs parse_arguments(int argc, char **argv);
GDPM_DLL_EXPORT void handle_arguments(const cxxargs& args);
GDPM_DLL_EXPORT void run_command(command_e command, const std::vector<std::string>& package_titles, const std::vector<std::string>& opts);
GDPM_DLL_EXPORT void print_package_list(const rapidjson::Document& json);
GDPM_DLL_EXPORT void print_package_list(const std::vector<package_info>& packages);
GDPM_DLL_EXPORT std::vector<package_info> synchronize_database(const std::vector<std::string>& package_titles);
}

16
include/plugin.hpp Normal file
View file

@ -0,0 +1,16 @@
#include <string>
namespace towk::plugin{
struct info{
std::string name;
std::string description;
std::string version;
};
extern int init(int argc, char **argv);
extern int set_name(const char *name);
extern int set_description(const char *description);
extern int set_version(const char *version);
extern int finalize();
}

59
include/progress_bar.hpp Normal file
View file

@ -0,0 +1,59 @@
#include <cmath>
#include <iomanip>
#include <ostream>
#include <string>
class progress_bar
{
static const auto overhead = sizeof " [100%]";
std::ostream& os;
const std::size_t bar_width;
std::string message;
const std::string full_bar;
public:
progress_bar(std::ostream& os, std::size_t line_width,
std::string message_, const char symbol = '.')
: os{os},
bar_width{line_width - overhead},
message{std::move(message_)},
full_bar{std::string(bar_width, symbol) + std::string(bar_width, ' ')}
{
if (message.size()+1 >= bar_width || message.find('\n') != message.npos) {
os << message << '\n';
message.clear();
} else {
message += ' ';
}
write(0.0);
}
// not copyable
progress_bar(const progress_bar&) = delete;
progress_bar& operator=(const progress_bar&) = delete;
~progress_bar()
{
write(1.0);
os << '\n';
}
void write(double fraction);
};
void progress_bar::write(double fraction)
{
// clamp fraction to valid range [0,1]
if (fraction < 0)
fraction = 0;
else if (fraction > 1)
fraction = 1;
auto width = bar_width - message.size();
auto offset = bar_width - static_cast<unsigned>(width * fraction);
os << '\r' << message;
os.write(full_bar.data() + offset, width);
os << " [" << std::setw(3) << static_cast<int>(100*fraction) << "%] " << std::flush;
}

88
include/rest_api.hpp Normal file
View file

@ -0,0 +1,88 @@
#include "constants.hpp"
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
namespace gdpm::rest_api{
// See GitHub reference: https://github.com/godotengine/godot-asset-library/blob/master/API.md
namespace endpoints{
constexpr const char *POST_Register = "/register";
constexpr const char *POST_Login = "/login";
constexpr const char *POST_Logout = "/logout";
constexpr const char *POST_ChangePassword = "/change_password";
constexpr const char *GET_Configure = "/configure";
constexpr const char *GET_Asset_NoParams = "/asset";
constexpr const char *GET_Asset = "/asset?";
constexpr const char *GET_AssetId = "/asset/{id}"; // ...find_replace
constexpr const char *POST_AssetIdDelete = "/asset/{id}/delete";
constexpr const char *POST_AssetIdUndelete = "/asset/{id}/delete";
constexpr const char *POST_AssetSupportLevel = "/asset/{id}/support_level";
constexpr const char *POST_Asset = "/asset";
constexpr const char *POST_AssetId = "/asset/{id}";
constexpr const char *POST_AssetEditId = "/asset/edit/{id}";
}
bool register_account(const std::string& username, const std::string& password, const std::string& email);
bool login(const std::string& username, const std::string& password);
bool logout();
// bool change_password()
enum type_e { any, addon, project };
enum support_e { all, official, community, testing };
enum sort_e { none, rating, cost, name, updated };
struct asset_list_context{
type_e type;
int category;
support_e support;
std::string filter;
std::string user;
std::string godot_version;
int max_results;
int page;
sort_e sort;
bool reverse;
int verbose;
};
asset_list_context make_context(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);
rapidjson::Document _parse_json(const std::string& r, int verbose = 0);
std::string _get_type_string(type_e type);
std::string _get_support_string(support_e support);
std::string _get_sort_string(sort_e sort);
void _print_params(const asset_list_context& params);
bool register_account(const std::string& username, const std::string& password, const std::string& email);
bool login(const std::string& username, const std::string& password);
bool logout();
rapidjson::Document configure(const std::string& url = constants::HostUrl, type_e type = any, int verbose = 0);
rapidjson::Document get_assets_list(const std::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 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);
rapidjson::Document get_assets_list(const std::string& url, const asset_list_context& params);
rapidjson::Document get_asset(const std::string& url, int asset_id, int verbose = GDPM_DEFAULT_ASSET_VERBOSE);
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
/*
POST /asset
POST /asset/{id}
POST /asset/edit/{id}
*/
void edit_asset(/* need input parameters... */);
/* GET /asset/{id} */
void get_asset_edit(int asset_id);
/* POST /asset/edit/{id}/review */
std::string review_asset_edit(int asset_id);
/* POST /asset/edit/{id}/accept */
std::string accept_asset_edit(int asset_id); // ...for moderators
/* POST /asset/edit/{id}/reject */
std::string reject_asset_edit(int asset_id);
}

104
include/utils.hpp Normal file
View file

@ -0,0 +1,104 @@
#pragma once
#include "constants.hpp"
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <string_view>
#include <string>
#include <fstream>
#include <chrono>
#include <vector>
#include <thread>
#include <fmt/format.h>
#include <fmt/chrono.h>
namespace gdpm::utils{
using namespace std::chrono_literals;
struct memory_buffer{
char *addr = nullptr;
size_t size = 0;
};
static memory_buffer make_buffer(){
return memory_buffer{
.addr = (char*)malloc(1), /* ...will grow as needed in curl_write_to_stream */
.size = 0
};
}
static void free_buffer(memory_buffer& buf){
free(buf.addr);
}
static size_t curl_write_to_buffer(char *contents, size_t size, size_t nmemb, void *userdata){
size_t realsize = size * nmemb;
struct memory_buffer *m = (struct memory_buffer*)userdata;
m->addr = (char*)realloc(m->addr, m->size + realsize + 1);
if(m->addr == nullptr){
/* Out of memory */
fprintf(stderr, "Not enough memory (realloc returned NULL)\n");
return 0;
}
memcpy(&(m->addr[m->size]), contents, realsize);
m->size += realsize;
m->addr[m->size] = 0;
return realsize;
}
static size_t curl_write_to_stream(char *ptr, size_t size, size_t nmemb, void *userdata){
if(nmemb == 0)
return 0;
return fwrite(ptr, size, nmemb, (FILE*)userdata);
}
static inline auto timestamp(const std::string& format = ":%I:%M:%S %p; %Y-%m-%d"){
time_t t = std::time(nullptr);
#if GDPM_ENABLE_TIMESTAMPS == 1
return fmt::format(fmt::runtime("{"+format+"}"), fmt::localtime(t));
#else
return "";
#endif
// return fmt::format(format, std::chrono::system_clock::now());
}
template <typename T, typename...Args>
void push_back(std::vector<T>& v, Args&&...args)
{
static_assert((std::is_constructible_v<T, Args&&>&& ...));
(v.emplace_back(std::forward<Args>(args)), ...);
}
// A make_tuple wrapper for enforcing certain requirements
template <typename... Args>
auto range(Args...args)
{
// Limit number of args to only 2
static_assert(sizeof...(Args) != 1, "Ranges requires only 2 arguments");
return std::make_tuple(std::forward<Args>(args)...);
}
template <class T, class F>
void move_if_not(std::vector<T>& from, std::vector<T>& to, F pred){
auto part = std::partition(from.begin(), from.end(), pred);
std::move(part, from.end(), std::back_inserter(from));
from.erase(part);
}
std::string readfile(const std::string& path);
void to_lower(std::string& s);
std::vector<std::string> parse_lines(const std::string& s);
std::string replace_first(std::string& s, const std::string& from, const std::string& to);
std::string replace_all(std::string& s, const std::string& from, const std::string& to);
int 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);
}