mirror of
https://github.com/davidallendj/gdpm.git
synced 2025-12-20 03:27:02 -07:00
Major refactor and API changes
- Updated `.gitignore` file - Updated `CMakeLists.txt` to build static exectuable - Changed some `Doxyfile` configurations to build more robust and complete documentation (WIP) - Changed how `remote` works to better reflect `git`'s API (WIP) - Changed how error handling works - Improved `bin/compile.sh` script - Improved `bin/lines.sh` script (kinda) - Removed some instances of `fmt` in favor of `std` string functions - Restructed style for better readibility
This commit is contained in:
parent
ba23299777
commit
5a73651ad1
29 changed files with 1836 additions and 1140 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,7 +1,8 @@
|
||||||
build/**
|
build/**
|
||||||
builds/**
|
builds/**
|
||||||
bin/gdpm
|
bin/gdpm
|
||||||
bin/gdpm-tests
|
bin/gdpm.static
|
||||||
|
bin/gdpm.tests
|
||||||
docs/doxygen
|
docs/doxygen
|
||||||
cache/**
|
cache/**
|
||||||
tests/*
|
tests/*
|
||||||
|
|
|
||||||
|
|
@ -43,25 +43,30 @@ set(LINK_LIBS
|
||||||
SQLiteCpp
|
SQLiteCpp
|
||||||
-lcurlpp
|
-lcurlpp
|
||||||
-lzip
|
-lzip
|
||||||
|
-lsqlite3
|
||||||
|
-lcurl
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set library and executable targets
|
# Set library and executable targets
|
||||||
add_library(${PROJECT_NAME}-shared SHARED "${SRC}")
|
add_library(${PROJECT_NAME}-shared SHARED "${SRC}")
|
||||||
add_library(${PROJECT_NAME}-static STATIC "${SRC}")
|
add_library(${PROJECT_NAME}-static STATIC "${SRC}")
|
||||||
add_executable(${PROJECT_NAME} "src/main.cpp")
|
add_executable(${PROJECT_NAME} "src/main.cpp")
|
||||||
add_executable(${PROJECT_NAME}-tests "${TESTS}")
|
add_executable(${PROJECT_NAME}.static "src/main.cpp")
|
||||||
|
add_executable(${PROJECT_NAME}.tests "${TESTS}")
|
||||||
|
|
||||||
# Set include directories for targets
|
# Set include directories for targets
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRS})
|
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRS})
|
||||||
|
target_include_directories(${PROJECT_NAME}.static PRIVATE ${INCLUDE_DIRS})
|
||||||
|
target_include_directories(${PROJECT_NAME}.tests PRIVATE ${INCLUDE_DIRS})
|
||||||
target_include_directories(${PROJECT_NAME}-shared PRIVATE ${INCLUDE_DIRS})
|
target_include_directories(${PROJECT_NAME}-shared PRIVATE ${INCLUDE_DIRS})
|
||||||
target_include_directories(${PROJECT_NAME}-static PRIVATE ${INCLUDE_DIRS})
|
target_include_directories(${PROJECT_NAME}-static PRIVATE ${INCLUDE_DIRS})
|
||||||
target_include_directories(${PROJECT_NAME}-tests PRIVATE ${INCLUDE_DIRS})
|
|
||||||
|
|
||||||
# Set link libraries for targets
|
# Set link libraries for targets
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS})
|
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS})
|
||||||
|
target_link_libraries(${PROJECT_NAME}.static PRIVATE ${PROJECT_NAME}-static ${LINK_LIBS})
|
||||||
|
target_link_libraries(${PROJECT_NAME}.tests PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS})
|
||||||
target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS})
|
target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS})
|
||||||
target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS})
|
target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS})
|
||||||
target_link_libraries(${PROJECT_NAME}-tests PRIVATE ${PROJECT_NAME}-shared ${LINK_LIBS})
|
|
||||||
|
|
||||||
# Add project unit tests
|
# Add project unit tests
|
||||||
# add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS})
|
# add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS})
|
||||||
|
|
|
||||||
12
Doxyfile
12
Doxyfile
|
|
@ -499,13 +499,13 @@ NUM_PROC_THREADS = 1
|
||||||
# normally produced when WARNINGS is set to YES.
|
# normally produced when WARNINGS is set to YES.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
EXTRACT_ALL = NO
|
EXTRACT_ALL = YES
|
||||||
|
|
||||||
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
|
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
|
||||||
# be included in the documentation.
|
# be included in the documentation.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
EXTRACT_PRIVATE = NO
|
EXTRACT_PRIVATE = YES
|
||||||
|
|
||||||
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
|
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
|
||||||
# methods of a class will be included in the documentation.
|
# methods of a class will be included in the documentation.
|
||||||
|
|
@ -1008,7 +1008,7 @@ FILE_PATTERNS = *.c \
|
||||||
# be searched for input files as well.
|
# be searched for input files as well.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
RECURSIVE = NO
|
RECURSIVE = YES
|
||||||
|
|
||||||
# The EXCLUDE tag can be used to specify files and/or directories that should be
|
# The EXCLUDE tag can be used to specify files and/or directories that should be
|
||||||
# excluded from the INPUT source files. This way you can easily exclude a
|
# excluded from the INPUT source files. This way you can easily exclude a
|
||||||
|
|
@ -1017,14 +1017,14 @@ RECURSIVE = NO
|
||||||
# Note that relative paths are relative to the directory from which doxygen is
|
# Note that relative paths are relative to the directory from which doxygen is
|
||||||
# run.
|
# run.
|
||||||
|
|
||||||
EXCLUDE =
|
EXCLUDE = bin build docs examples modules tests
|
||||||
|
|
||||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||||
# from the input.
|
# from the input.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
EXCLUDE_SYMLINKS = NO
|
EXCLUDE_SYMLINKS = YES
|
||||||
|
|
||||||
# If the value of the INPUT tag contains directories, you can use the
|
# If the value of the INPUT tag contains directories, you can use the
|
||||||
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
|
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
|
||||||
|
|
@ -1153,7 +1153,7 @@ FORTRAN_COMMENT_AFTER = 72
|
||||||
# also VERBATIM_HEADERS is set to NO.
|
# also VERBATIM_HEADERS is set to NO.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
SOURCE_BROWSER = NO
|
SOURCE_BROWSER = YES
|
||||||
|
|
||||||
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
|
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
|
||||||
# classes and enums directly into the documentation.
|
# classes and enums directly into the documentation.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,38 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
exe=gdpm
|
||||||
|
static=gdpm.static
|
||||||
|
tests=gdpm.tests
|
||||||
|
|
||||||
|
function test_link(){
|
||||||
|
path=$1
|
||||||
|
link=$2
|
||||||
|
if test -f "$path"
|
||||||
|
then
|
||||||
|
echo "Creating link from '$path' to '$link')"
|
||||||
|
if test -f "$link"
|
||||||
|
then
|
||||||
|
rm $link
|
||||||
|
fi
|
||||||
|
ln -s $path $link
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_strip(){
|
||||||
|
path=$1
|
||||||
|
if test -f "$path"
|
||||||
|
then
|
||||||
|
echo "Stripping debug symbols from '$path'"
|
||||||
|
strip "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Run this script at project root
|
# Run this script at project root
|
||||||
#meson configure build
|
#meson configure build
|
||||||
#CXX=clang++ meson compile -C build -j$(proc)
|
#CXX=clang++ meson compile -C build -j$(proc)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# CMake/ninja build system
|
# CMake/ninja build system
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
cmake -B build -S . -D CMAKE_EXPORT_COMPILE_COMMANDS=1 -D CMAKE_BUILD_TYPE=Debug -G Ninja
|
cmake -B build -S . -D CMAKE_EXPORT_COMPILE_COMMANDS=1 -D CMAKE_BUILD_TYPE=Debug -G Ninja
|
||||||
|
|
@ -13,12 +40,17 @@ ninja -C build -j $(nproc)
|
||||||
|
|
||||||
|
|
||||||
# Create symlinks to executables in build folder if necessary
|
# Create symlinks to executables in build folder if necessary
|
||||||
if test -f "../build/gdpm"; then
|
test_link $script_dir/../build/gdpm $script_dir/../bin/$exe
|
||||||
rm bin/gdpm
|
test_link $script_dir/../build/gdpm.static $script_dir/../bin/$static
|
||||||
ln -s ../build/gdpm bin/gdpm
|
test_link $script_dir/../build/gdpm.tests $script_dir/../bin/$tests
|
||||||
fi
|
|
||||||
|
|
||||||
if test -f "../build/gdpm-tests"; then
|
|
||||||
rm bin/gdpm-tests
|
# Strip debug symbols
|
||||||
ln -s ../build/gdpm-tests bin/gdpm-tests
|
test_strip ${script_dir}/../build/gdpm
|
||||||
fi
|
test_strip ${script_dir}/../build/gdpm.static
|
||||||
|
test_strip ${script_dir}/../build/gdpm.tests
|
||||||
|
|
||||||
|
|
||||||
|
# Generate documentation using `doxygen`
|
||||||
|
cd ${script_dir}/..
|
||||||
|
doxygen
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Call this from project root
|
# Call this from project root
|
||||||
wc -l include/*.hpp src/*.cpp
|
grep -v ^S include/*.hpp src/*.cpp | wc -l include/*.hpp src/*.cpp
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,31 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
|
#include "package.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace gdpm::package_manager{
|
|
||||||
struct package_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace gdpm::cache{
|
namespace gdpm::cache {
|
||||||
using namespace package_manager;
|
struct params {
|
||||||
int create_package_database(const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
std::string cache_path = GDPM_PACKAGE_CACHE_PATH;
|
||||||
int insert_package_info(const std::vector<package_info>& package, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
std::string table_name = GDPM_PACKAGE_CACHE_TABLENAME;
|
||||||
std::vector<package_info> get_package_info_by_id(const std::vector<size_t>& package_ids, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
};
|
||||||
std::vector<package_info> get_package_info_by_title(const std::vector<std::string>& package_titles, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
std::vector<package_info> get_installed_packages(const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
int update_package_info(const std::vector<package_info>& packages, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
int update_sync_info(const std::vector<std::string>& download_urls, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
int delete_packages(const std::vector<std::string>& package_titles, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
int delete_packages(const std::vector<size_t>& package_ids, const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
int drop_package_database(const std::string& cache_path = GDPM_PACKAGE_CACHE_PATH, const std::string& table_name = GDPM_PACKAGE_CACHE_TABLENAME);
|
|
||||||
|
|
||||||
std::string to_values(const package_info& package);
|
error create_package_database(bool overwrite = false, const params& = params());
|
||||||
std::string to_values(const std::vector<package_info>& packages);
|
error insert_package_info(const package::info_list& packages, const params& = params());
|
||||||
|
result_t<package::info_list> get_package_info_by_id(const package::id_list& package_ids, const params& = params());
|
||||||
|
result_t<package::info_list> get_package_info_by_title(const package::title_list& package_titles, const params& params = cache::params());
|
||||||
|
result_t<package::info_list> get_installed_packages(const params& = params());
|
||||||
|
error update_package_info(const package::info_list& packages, const params& = params());
|
||||||
|
error update_sync_info(const std::vector<std::string>& download_urls, const params& = params());
|
||||||
|
error delete_packages(const package::title_list& package_titles, const params& = params());
|
||||||
|
error delete_packages(const package::id_list& package_ids, const params& = params());
|
||||||
|
error drop_package_database(const params& = params());
|
||||||
|
|
||||||
|
result_t<std::string> to_values(const package::info& package);
|
||||||
|
result_t<std::string> to_values(const package::info_list& packages);
|
||||||
}
|
}
|
||||||
|
|
@ -3,32 +3,33 @@
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
namespace gdpm::config{
|
namespace gdpm::config{
|
||||||
struct context{
|
struct context{
|
||||||
std::string username;
|
string username;
|
||||||
std::string password;
|
string password;
|
||||||
std::string path;
|
string path;
|
||||||
std::string token;
|
string token;
|
||||||
std::string godot_version;
|
string godot_version;
|
||||||
std::string packages_dir;
|
string packages_dir;
|
||||||
std::string tmp_dir;
|
string tmp_dir;
|
||||||
std::set<std::string> remote_sources;
|
string_map remote_sources;
|
||||||
size_t threads;
|
size_t threads;
|
||||||
size_t timeout;
|
size_t timeout;
|
||||||
bool enable_sync;
|
bool enable_sync;
|
||||||
bool enable_file_logging;
|
bool enable_file_logging;
|
||||||
int verbose;
|
int verbose;
|
||||||
};
|
};
|
||||||
std::string to_json(const context& params);
|
string to_json(const context& params);
|
||||||
gdpm::error load(std::filesystem::path path, context& config, int verbose = 0);
|
error load(std::filesystem::path path, context& config, int verbose = 0);
|
||||||
gdpm::error save(std::filesystem::path path, const context& config, int verbose = 0);
|
error save(std::filesystem::path path, const context& config, int verbose = 0);
|
||||||
context make_context(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::set<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);
|
context make_context(const std::string& username = GDPM_CONFIG_USERNAME, const string& password = GDPM_CONFIG_PASSWORD, const string& path = GDPM_CONFIG_PATH, const string& token = GDPM_CONFIG_TOKEN, const string& godot_version = GDPM_CONFIG_GODOT_VERSION, const string& packages_dir = GDPM_CONFIG_LOCAL_PACKAGES_DIR, const string& tmp_dir = GDPM_CONFIG_LOCAL_TMP_DIR, const string_map& 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);
|
||||||
|
error validate(const rapidjson::Document& doc);
|
||||||
|
|
||||||
extern context config;
|
extern context config;
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
namespace gdpm::constants{
|
namespace gdpm::constants{
|
||||||
|
const std::string RemoteName(std::string("origin"));
|
||||||
const std::string HomePath(std::string(std::getenv("HOME")) + "/");
|
const std::string HomePath(std::string(std::getenv("HOME")) + "/");
|
||||||
const std::string TestPath(HomePath + ".config/gdpm/tests");
|
const std::string TestPath(HomePath + ".config/gdpm/tests");
|
||||||
const std::string ConfigPath(HomePath + ".config/gdpm/config.json");
|
const std::string ConfigPath(HomePath + ".config/gdpm/config.json");
|
||||||
|
|
@ -24,7 +25,7 @@ namespace gdpm::constants{
|
||||||
#define GDPM_CONFIG_GODOT_VERSION "3.4"
|
#define GDPM_CONFIG_GODOT_VERSION "3.4"
|
||||||
#define GDPM_CONFIG_LOCAL_PACKAGES_DIR gdpm::constants::LocalPackagesDir
|
#define GDPM_CONFIG_LOCAL_PACKAGES_DIR gdpm::constants::LocalPackagesDir
|
||||||
#define GDPM_CONFIG_LOCAL_TMP_DIR gdpm::constants::TemporaryPath
|
#define GDPM_CONFIG_LOCAL_TMP_DIR gdpm::constants::TemporaryPath
|
||||||
#define GDPM_CONFIG_REMOTE_SOURCES constants::HostUrl
|
#define GDPM_CONFIG_REMOTE_SOURCES std::pair<std::string, std::string>(constants::RemoteName, constants::HostUrl)
|
||||||
#define GDPM_CONFIG_THREADS 1
|
#define GDPM_CONFIG_THREADS 1
|
||||||
#define GDPM_CONFIG_TIMEOUT_MS 30000
|
#define GDPM_CONFIG_TIMEOUT_MS 30000
|
||||||
#define GDPM_CONFIG_ENABLE_SYNC 1
|
#define GDPM_CONFIG_ENABLE_SYNC 1
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,90 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <string>
|
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <new>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace gdpm::constants::error{
|
||||||
|
|
||||||
namespace gdpm::error_codes{
|
|
||||||
enum {
|
enum {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
UNKNOWN = 1,
|
UNKNOWN,
|
||||||
NOT_FOUND = 2,
|
UNKNOWN_COMMAND,
|
||||||
FILE_EXISTS = 3,
|
NOT_FOUND,
|
||||||
HOST_UNREACHABLE = 4,
|
NOT_DEFINED,
|
||||||
|
NOT_IMPLEMENTED,
|
||||||
|
NO_PACKAGE_FOUND,
|
||||||
|
PATH_NOT_DEFINED,
|
||||||
|
FILE_EXISTS,
|
||||||
|
FILE_NOT_FOUND,
|
||||||
|
DIRECTORY_EXISTS,
|
||||||
|
DIRECTORY_NOT_FOULD,
|
||||||
|
HOST_UNREACHABLE,
|
||||||
|
EMPTY_RESPONSE,
|
||||||
|
INVALID_ARGS,
|
||||||
|
INVALID_CONFIG,
|
||||||
|
INVALID_KEY,
|
||||||
|
HTTP_RESPONSE_ERROR,
|
||||||
|
STD_ERROR
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::string to_string(int error_code) {
|
const string_list messages {
|
||||||
return "";
|
"",
|
||||||
|
"An unknown error has occurred.",
|
||||||
|
"Unknown command.",
|
||||||
|
"Resource not found.",
|
||||||
|
"Function not defined.",
|
||||||
|
"Function not implemented.",
|
||||||
|
"No package found.",
|
||||||
|
"Path is not well-defined",
|
||||||
|
"File found.",
|
||||||
|
"File does not exist.",
|
||||||
|
"Directory exists.",
|
||||||
|
"Directory not found.",
|
||||||
|
"No response from host. Host is unreacheable",
|
||||||
|
"Empty response from host.",
|
||||||
|
"Invalid arguments.",
|
||||||
|
"Invalid configuration.",
|
||||||
|
"Invalid key.",
|
||||||
|
"An HTTP response error has occurred.",
|
||||||
|
"An error has occurred."
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
inline string get_message(int error_code) {
|
||||||
|
string message{};
|
||||||
|
try{ message = messages[error_code]; }
|
||||||
|
catch(const std::bad_alloc& e){
|
||||||
|
log::error("No default message for error code.");
|
||||||
|
}
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace gdpm{
|
namespace gdpm{
|
||||||
class error {
|
class error {
|
||||||
public:
|
public:
|
||||||
error(int code = 0, const std::string& message = "", bool print_message = false):
|
constexpr explicit error(int code = 0, const string& message = "{code}"):
|
||||||
m_code(code), m_message(message)
|
m_code(code), m_message(message == "{code}" ? constants::error::get_message(code): message)
|
||||||
{
|
{}
|
||||||
if(print_message)
|
|
||||||
print();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_code(int code) { m_code = code; }
|
void set_code(int code) { m_code = code; }
|
||||||
void set_message(const std::string& message) { m_message = message; }
|
void set_message(const string& message) { m_message = message; }
|
||||||
|
|
||||||
int get_code() const { return m_code; }
|
int get_code() const { return m_code; }
|
||||||
std::string get_message() const { return m_message; }
|
string get_message() const { return m_message; }
|
||||||
bool has_error() const { return m_code != 0; }
|
bool has_occurred() const { return m_code != 0; }
|
||||||
void print(){ log::println(GDPM_COLOR_LOG_ERROR "ERROR: {}" GDPM_COLOR_LOG_RESET, m_message); }
|
|
||||||
|
bool operator()(){
|
||||||
|
return has_occurred();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_code;
|
int m_code;
|
||||||
std::string m_message;
|
string m_message;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add logging function that can handle error objects
|
// Add logging function that can handle error objects
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace gdpm::http{
|
namespace gdpm::http{
|
||||||
|
using headers_t = std::unordered_map<string, string>;
|
||||||
|
enum response_code{
|
||||||
|
OK = 200,
|
||||||
|
NOT_FOUND = 400
|
||||||
|
};
|
||||||
struct response{
|
struct response{
|
||||||
long code = 0;
|
long code = 0;
|
||||||
std::string body{};
|
string body{};
|
||||||
std::unordered_map<std::string, std::string> headers{};
|
headers_t headers{};
|
||||||
|
error error();
|
||||||
};
|
};
|
||||||
|
|
||||||
response request_get(const std::string& url, size_t timeout = GDPM_CONFIG_TIMEOUT_MS, int verbose = 0);
|
|
||||||
response request_post(const std::string& url, const char *post_fields="", size_t timeout = GDPM_CONFIG_TIMEOUT_MS, int verbose = 0);
|
struct params {
|
||||||
response download_file(const std::string& url, const std::string& storage_path, size_t timeout = GDPM_CONFIG_TIMEOUT_MS, int verbose = 0);
|
size_t timeout = GDPM_CONFIG_TIMEOUT_MS;
|
||||||
|
int verbose = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
string url_escape(const string& url);
|
||||||
|
response request_get(const string& url, const http::params& params = http::params());
|
||||||
|
response request_post(const string& url, const char *post_fields="", const http::params& params = http::params());
|
||||||
|
response download_file(const string& url, const string& storage_path, const http::params& params = http::params());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
|
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "colors.hpp"
|
#include "colors.hpp"
|
||||||
#include <fmt/core.h>
|
// #include <fmt/core.h>
|
||||||
|
|
||||||
#if __cplusplus > 201703L
|
#if __cplusplus > 201703L
|
||||||
// #include <format>
|
// #include <format>
|
||||||
#else
|
#else
|
||||||
#endif
|
#endif
|
||||||
#include <fmt/printf.h>
|
// #include <fmt/printf.h>
|
||||||
#include <fmt/format.h>
|
// #include <fmt/format.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: Allow setting the logging *prefix*
|
TODO: Allow setting the logging *prefix*
|
||||||
|
|
|
||||||
113
include/package.hpp
Normal file
113
include/package.hpp
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "package.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace gdpm::package {
|
||||||
|
|
||||||
|
struct info {
|
||||||
|
size_t asset_id;
|
||||||
|
string type;
|
||||||
|
string title;
|
||||||
|
string author;
|
||||||
|
size_t author_id;
|
||||||
|
string version;
|
||||||
|
string godot_version;
|
||||||
|
string cost;
|
||||||
|
string description;
|
||||||
|
string modify_date;
|
||||||
|
string support_level;
|
||||||
|
string category;
|
||||||
|
string remote_source;
|
||||||
|
string download_url;
|
||||||
|
string download_hash;
|
||||||
|
bool is_installed;
|
||||||
|
string install_path;
|
||||||
|
std::vector<info> dependencies;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct params {
|
||||||
|
enum install_method_e{
|
||||||
|
GLOBAL_LINK_LOCAL,
|
||||||
|
GLOBAL_CLONE_LOCAL,
|
||||||
|
GLOBAL_ONLY,
|
||||||
|
LOCAL_ONLY
|
||||||
|
};
|
||||||
|
bool parallel_jobs = 1;
|
||||||
|
bool use_cache = true;
|
||||||
|
bool use_remote = true;
|
||||||
|
bool skip_prompt = false;
|
||||||
|
std::string remote_source = "";
|
||||||
|
install_method_e install_method = GLOBAL_LINK_LOCAL;
|
||||||
|
};
|
||||||
|
|
||||||
|
using info_list = std::vector<info>;
|
||||||
|
using title_list = std::vector<string>;
|
||||||
|
using id_list = std::vector<size_t>;
|
||||||
|
using path = std::string;
|
||||||
|
using path_list = std::vector<path>;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Install a Godot package from the Asset Library in the current project.
|
||||||
|
By default, packages are stored in a global directory and linked to the project
|
||||||
|
where the tool is executed. Use the `--jobs` option to install packages in
|
||||||
|
parallel. Specify which remote source to use by passing the `--remote` option.
|
||||||
|
By default, the first remote source will by used. Alternatively, if the
|
||||||
|
`--use-remote=false` option is passed, then the tool will only attempt to fetch the
|
||||||
|
package from cache. Use the `--use-cache=false` option to fetch package only from
|
||||||
|
remote source.
|
||||||
|
|
||||||
|
`gdpm install "super cool example package"
|
||||||
|
|
||||||
|
To only install a package global without linking to the project, use the
|
||||||
|
`--global-only` option.
|
||||||
|
|
||||||
|
`gdpm install --global-only "super cool example package"
|
||||||
|
|
||||||
|
To install a package to a local project only, use the `--local-only` option.
|
||||||
|
This will extract the package contents to the project location instead of the
|
||||||
|
global install location. Use the `--path` option to specify an alternative
|
||||||
|
path.
|
||||||
|
|
||||||
|
`gdpm install --local-only "super cool example package" --path addons/example
|
||||||
|
|
||||||
|
To copy the package to a project instead of linking, use the `--clone` option.
|
||||||
|
|
||||||
|
`gdpm install --clone "super cool examle package"
|
||||||
|
|
||||||
|
*/
|
||||||
|
GDPM_DLL_EXPORT error install(const config::context& config, const title_list& package_titles, const params& params = package::params());
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Remove's package and contents from local database.
|
||||||
|
*/
|
||||||
|
GDPM_DLL_EXPORT error remove(const config::context& config, const title_list& package_titles, const params& params = package::params());
|
||||||
|
GDPM_DLL_EXPORT error remove_all(const config::context& config, const params& params = package::params());
|
||||||
|
GDPM_DLL_EXPORT error update(const config::context& config, const title_list& package_titles, const params& params = package::params());
|
||||||
|
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 args_t& args, const opts_t& opts);
|
||||||
|
GDPM_DLL_EXPORT error export_to(const path_list& paths);
|
||||||
|
GDPM_DLL_EXPORT error link(const config::context& config, const title_list& package_titles, const opts_t& opts);
|
||||||
|
GDPM_DLL_EXPORT error clone(const config::context& config, const title_list& package_titles, const opts_t& opts);
|
||||||
|
|
||||||
|
|
||||||
|
GDPM_DLL_EXPORT void print_list(const rapidjson::Document& json);
|
||||||
|
GDPM_DLL_EXPORT void print_list(const info_list& packages);
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "package.hpp"
|
||||||
#include "package_manager.hpp"
|
#include "package_manager.hpp"
|
||||||
|
#include "remote.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cxxopts.hpp>
|
#include <cxxopts.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -12,39 +15,23 @@
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
namespace gdpm::package_manager{
|
namespace gdpm::package_manager {
|
||||||
extern std::vector<std::string> repo_sources;
|
extern remote::repository_map repo_sources;
|
||||||
extern CURL *curl;
|
extern CURL *curl;
|
||||||
extern CURLcode res;
|
extern CURLcode res;
|
||||||
extern config::context config;
|
extern 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;
|
|
||||||
std::vector<package_info> dependencies;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct cxxargs{
|
struct cxxargs{
|
||||||
cxxopts::ParseResult result;
|
cxxopts::ParseResult result;
|
||||||
cxxopts::Options options;
|
cxxopts::Options options;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum command_e{
|
struct exec_args{
|
||||||
|
args_t args;
|
||||||
|
opts_t opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class action_e{
|
||||||
install,
|
install,
|
||||||
remove,
|
remove,
|
||||||
update,
|
update,
|
||||||
|
|
@ -60,45 +47,12 @@ namespace gdpm::package_manager{
|
||||||
none
|
none
|
||||||
};
|
};
|
||||||
|
|
||||||
using package_list = std::vector<package_info>;
|
GDPM_DLL_EXPORT result_t<exec_args> initialize(int argc, char **argv);
|
||||||
using package_titles = std::vector<std::string>;
|
GDPM_DLL_EXPORT int execute(const args_t& args, const opts_t& opts);
|
||||||
using cl_arg = std::variant<int, bool, float, std::string>;
|
|
||||||
using cl_args = std::vector<cl_arg>;
|
|
||||||
using cl_opts = std::unordered_map<std::string, cl_args>;
|
|
||||||
|
|
||||||
GDPM_DLL_EXPORT int initialize(int argc, char **argv);
|
|
||||||
GDPM_DLL_EXPORT int execute();
|
|
||||||
GDPM_DLL_EXPORT void finalize();
|
GDPM_DLL_EXPORT void finalize();
|
||||||
|
|
||||||
/* Package management API */
|
|
||||||
GDPM_DLL_EXPORT error install_packages(const std::vector<std::string>& package_titles, bool skip_prompt = false);
|
|
||||||
GDPM_DLL_EXPORT error remove_packages(const std::vector<std::string>& package_titles, bool skip_prompt = false);
|
|
||||||
GDPM_DLL_EXPORT error remove_all_packages();
|
|
||||||
GDPM_DLL_EXPORT error update_packages(const std::vector<std::string>& package_titles, bool skip_prompt = false);
|
|
||||||
GDPM_DLL_EXPORT error search_for_packages(const std::vector<std::string>& package_titles, bool skip_prompt = false);
|
|
||||||
GDPM_DLL_EXPORT error export_packages(const std::vector<std::string>& paths);
|
|
||||||
GDPM_DLL_EXPORT std::vector<std::string> list_information(const std::vector<std::string>& opts, bool print_list = true);
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* Remote API */
|
|
||||||
GDPM_DLL_EXPORT error _handle_remote(const std::vector<std::string>& args, const std::vector<std::string>& opts);
|
|
||||||
GDPM_DLL_EXPORT void remote_add_repository(const std::vector<std::string>& repositories);
|
|
||||||
GDPM_DLL_EXPORT void remote_remove_respository(const std::vector<std::string>& repositories);
|
|
||||||
GDPM_DLL_EXPORT void remote_remove_respository(ssize_t index);
|
|
||||||
GDPM_DLL_EXPORT void remote_move_repository(int old_position, int new_position);
|
|
||||||
|
|
||||||
/* Auxiliary Functions */
|
/* Auxiliary Functions */
|
||||||
GDPM_DLL_EXPORT cxxargs _parse_arguments(int argc, char **argv);
|
GDPM_DLL_EXPORT cxxargs _parse_arguments(int argc, char **argv);
|
||||||
GDPM_DLL_EXPORT void _handle_arguments(const cxxargs& args);
|
GDPM_DLL_EXPORT result_t<exec_args> _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 run_command(action_e command, const package::title_list& package_titles, const opts_t& 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 void print_remote_sources();
|
|
||||||
GDPM_DLL_EXPORT std::vector<std::string> get_package_titles(const std::vector<package_info>& packages);
|
|
||||||
|
|
||||||
/* Dependency Management API */
|
|
||||||
GDPM_DLL_EXPORT std::vector<package_info> synchronize_database(const std::vector<std::string>& package_titles);
|
|
||||||
GDPM_DLL_EXPORT std::vector<std::string> resolve_dependencies(const std::vector<std::string>& package_titles);
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace towk::plugin{
|
namespace gdpm::plugin{
|
||||||
struct info{
|
struct info{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string description;
|
std::string description;
|
||||||
|
|
|
||||||
21
include/remote.hpp
Normal file
21
include/remote.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "config.hpp"
|
||||||
|
|
||||||
|
namespace gdpm::remote{
|
||||||
|
using repo_names = string_list;
|
||||||
|
using repo_urls = string_list;
|
||||||
|
using repository_map = string_map;
|
||||||
|
|
||||||
|
GDPM_DLL_EXPORT error _handle_remote(config::context& config, const args_t& args, const opts_t& opts);
|
||||||
|
GDPM_DLL_EXPORT void set_repositories(config::context& context, const repository_map& repos);
|
||||||
|
GDPM_DLL_EXPORT void add_repositories(config::context& context, const repository_map& repos);
|
||||||
|
GDPM_DLL_EXPORT void remove_respositories(config::context& context, const repo_names& name);
|
||||||
|
GDPM_DLL_EXPORT void move_repository(config::context& context, int old_position, int new_position);
|
||||||
|
GDPM_DLL_EXPORT void print_repositories(const config::context& context);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/writer.h>
|
#include <rapidjson/writer.h>
|
||||||
#include <rapidjson/stringbuffer.h>
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
|
@ -47,6 +48,7 @@ namespace gdpm::rest_api{
|
||||||
int verbose;
|
int verbose;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context make_from_config(const config::context& config);
|
||||||
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);
|
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);
|
||||||
|
|
||||||
std::string to_string(type_e type);
|
std::string to_string(type_e type);
|
||||||
|
|
|
||||||
73
include/result.hpp
Normal file
73
include/result.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace gdpm{
|
||||||
|
|
||||||
|
template <class T, error_t U = error>
|
||||||
|
class result_t {
|
||||||
|
public:
|
||||||
|
result_t() = delete;
|
||||||
|
result_t(
|
||||||
|
std::tuple<T, U> tuple,
|
||||||
|
std::function<T()> ok = []() -> T{},
|
||||||
|
std::function<void()> error = [](){}
|
||||||
|
): data(tuple), fn_ok(ok), fn_error(error)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
result_t(
|
||||||
|
T target,
|
||||||
|
U error,
|
||||||
|
std::function<std::unique_ptr<T>()> _fn_ok = []() -> std::unique_ptr<T>{ return nullptr; },
|
||||||
|
std::function<void()> _fn_error = [](){}
|
||||||
|
): data(std::make_tuple(target, error)), fn_ok(_fn_ok), fn_error(_fn_error)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void define(
|
||||||
|
std::function<T()> ok,
|
||||||
|
std::function<U()> error
|
||||||
|
){
|
||||||
|
fn_ok = ok;
|
||||||
|
fn_error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
/* Then, attempt unwrap the data. */
|
||||||
|
U err = std::get<U>(data);
|
||||||
|
if (err.has_occurred())
|
||||||
|
if(fn_error){
|
||||||
|
fn_error();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return fn_ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T unwrap_or(T default_value) const {
|
||||||
|
U err = std::get<U>(data);
|
||||||
|
if(err.has_occurred())
|
||||||
|
return default_value;
|
||||||
|
return fn_ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T unwrap_unsafe() const {
|
||||||
|
return std::get<T>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::tuple<T, U> data;
|
||||||
|
std::function<std::unique_ptr<T>()> fn_ok;
|
||||||
|
std::function<void()> fn_error;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace gdpm{
|
namespace gdpm{
|
||||||
|
class error;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Base class to prevent derived class from creating copies.
|
Base class to prevent derived class from creating copies.
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,4 +28,20 @@ namespace gdpm{
|
||||||
non_movable(const non_movable&) = delete;
|
non_movable(const non_movable&) = delete;
|
||||||
non_movable(non_movable&&) = delete;
|
non_movable(non_movable&&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 var = std::variant<int, bool, float, std::string>;
|
||||||
|
template <typename T = var>
|
||||||
|
using _args_t = std::vector<T>;
|
||||||
|
using args_t = _args_t<string>;
|
||||||
|
template <typename Key = std::string, class Value = _args_t<var>>
|
||||||
|
using _opts_t = std::unordered_map<Key, Value>;
|
||||||
|
using opts_t = _opts_t<string, string_list>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,21 +102,6 @@ namespace gdpm::utils{
|
||||||
std::string prompt_user(const char *message);
|
std::string prompt_user(const char *message);
|
||||||
bool prompt_user_yn(const char *message);
|
bool prompt_user_yn(const char *message);
|
||||||
void delay(std::chrono::milliseconds milliseconds = GDPM_REQUEST_DELAY);
|
void delay(std::chrono::milliseconds milliseconds = GDPM_REQUEST_DELAY);
|
||||||
|
std::string join(const std::vector<std::string>& target, const std::string& delimiter = ", ");
|
||||||
// TODO: Add function to get size of decompressed zip
|
// TODO: Add function to get size of decompressed zip
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace gdpm{
|
|
||||||
class non_copyable{
|
|
||||||
public:
|
|
||||||
non_copyable(){}
|
|
||||||
|
|
||||||
private:
|
|
||||||
non_copyable(const non_copyable&);
|
|
||||||
non_copyable& operator=(const non_copyable&);
|
|
||||||
};
|
|
||||||
|
|
||||||
class non_movable{
|
|
||||||
non_movable(const non_movable&) = delete;
|
|
||||||
non_movable(non_movable&&) = delete;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
284
src/cache.cpp
284
src/cache.cpp
|
|
@ -2,35 +2,44 @@
|
||||||
#include "cache.hpp"
|
#include "cache.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "package_manager.hpp"
|
#include "package.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <format>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
|
||||||
namespace gdpm::cache{
|
namespace gdpm::cache{
|
||||||
int create_package_database(const std::string& cache_path, const std::string& table_name){
|
error create_package_database(bool overwrite, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg;
|
char *errmsg;
|
||||||
|
|
||||||
/* Check and make sure directory is created before attempting to open */
|
/* Check and make sure directory is created before attempting to open */
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
fs::path dir_path = fs::path(cache_path).parent_path();
|
fs::path dir_path = fs::path(params.cache_path).parent_path();
|
||||||
if(!fs::exists(dir_path)){
|
if(!fs::exists(dir_path)){
|
||||||
log::info("Creating cache directories...{}", cache_path);
|
log::info("Creating cache directories...{}", params.cache_path);
|
||||||
fs::create_directories(dir_path);
|
fs::create_directories(dir_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("create_package_database.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc,
|
||||||
|
std::format(
|
||||||
|
"create_package_database.sqlite3_open(): {}",
|
||||||
|
sqlite3_errmsg(db)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sql = "CREATE TABLE IF NOT EXISTS " +
|
string sql = "CREATE TABLE IF NOT EXISTS " +
|
||||||
table_name + "("
|
params.table_name + "("
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
"asset_id INT NOT NULL,"
|
"asset_id INT NOT NULL,"
|
||||||
"type INT NOT NULL,"
|
"type INT NOT NULL,"
|
||||||
|
|
@ -54,60 +63,71 @@ namespace gdpm::cache{
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
// log::error("Failed to fetch data: {}\n", sqlite3_errmsg(db));
|
// log::error("Failed to fetch data: {}\n", sqlite3_errmsg(db));
|
||||||
log::error("create_package_database.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"create_package_database.sqlite3_exec(): {}",
|
||||||
|
errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int insert_package_info(const std::vector<package_info>& packages, const std::string& cache_path, const std::string& table_name){
|
error insert_package_info(const package::info_list& packages, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
|
|
||||||
/* Prepare values to use in sql statement */
|
/* Prepare values to use in sql statement */
|
||||||
std::string sql{"BEGIN TRANSACTION; "};
|
string sql{"BEGIN TRANSACTION; "};
|
||||||
for(const auto& p : packages){
|
for(const auto& p : packages){
|
||||||
sql += "INSERT INTO " + table_name + " (" GDPM_PACKAGE_CACHE_COLNAMES ") ";
|
sql += "INSERT INTO " + params.table_name + " (" GDPM_PACKAGE_CACHE_COLNAMES ") ";
|
||||||
sql += "VALUES (" + to_values(p) + "); ";
|
sql += "VALUES (" + to_values(p).unwrap_unsafe() + "); ";
|
||||||
}
|
}
|
||||||
sql += "COMMIT;";
|
sql += "COMMIT;";
|
||||||
// log::println("{}", sql);
|
// log::println("{}", sql);
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("insert_package_info.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"insert_package_info.sqlite3_open(): {}",
|
||||||
|
sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("insert_package_info.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"insert_package_info.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<package_info> get_package_info_by_id(const std::vector<size_t>& package_ids, const std::string& cache_path, const std::string& table_name){
|
result_t<package::info_list> get_package_info_by_id(const package::id_list& package_ids, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
size_t p_size = 0;
|
size_t p_size = 0;
|
||||||
std::vector<package_info> p_vector;
|
package::info_list p_vector;
|
||||||
std::string sql{"BEGIN TRANSACTION;\n"};
|
string sql{"BEGIN TRANSACTION;\n"};
|
||||||
|
|
||||||
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
||||||
// log::error("{}", (const char*)data);
|
// log::error("{}", (const char*)data);
|
||||||
// p_data *_data = (p_data*)data;
|
// p_data *_data = (p_data*)data;
|
||||||
std::vector<package_info> *_p_vector = (std::vector<package_info>*) data;
|
package::info_list *_p_vector = (package::info_list*) data;
|
||||||
package_info p{
|
package::info p{
|
||||||
.asset_id = std::stoul(argv[1]),
|
.asset_id = std::stoul(argv[1]),
|
||||||
.type = argv[2],
|
.type = argv[2],
|
||||||
.title = argv[3],
|
.title = argv[3],
|
||||||
|
|
@ -130,41 +150,47 @@ namespace gdpm::cache{
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("get_package_info_by_id.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"get_package_info_by_id.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const auto& p_id : package_ids){
|
for(const auto& p_id : package_ids){
|
||||||
sql += "SELECT * FROM " + table_name + " WHERE asset_id=" + fmt::to_string(p_id)+ ";\n";
|
sql += "SELECT * FROM " + params.table_name + " WHERE asset_id=" + std::to_string(p_id)+ ";\n";
|
||||||
}
|
}
|
||||||
sql += "COMMIT;\n";
|
sql += "COMMIT;\n";
|
||||||
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
|
error error(rc, std::format(
|
||||||
|
"get_package_info_by_id.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
log::error("get_package_info_by_id.sqlite3_exec(): {}", errmsg);
|
log::error("get_package_info_by_id.sqlite3_exec(): {}", errmsg);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return p_vector;
|
return result_t(p_vector, error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<package_info> get_package_info_by_title(const std::vector<std::string>& package_titles, const std::string& cache_path, const std::string& table_name){
|
result_t<package::info_list> get_package_info_by_title(const package::title_list& package_titles, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
std::vector<package_info> p_vector;
|
package::info_list p_vector;
|
||||||
|
|
||||||
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
||||||
if(argc <= 0)
|
if(argc <= 0)
|
||||||
return 1;
|
return 1;
|
||||||
std::vector<package_info> *_p_vector = (std::vector<package_info>*)data;
|
package::info_list *_p_vector = (package::info_list*)data;
|
||||||
// log::println("get_package_info_by_title.callback.argv: \n\t{}\n\t{}\n\t{}\n\t{}\n\t{}", argv[0], argv[1], argv[2],argv[3], argv[4]);
|
// log::println("get_package_info_by_title.callback.argv: \n\t{}\n\t{}\n\t{}\n\t{}\n\t{}", argv[0], argv[1], argv[2],argv[3], argv[4]);
|
||||||
package_info p{
|
package::info p{
|
||||||
.asset_id = std::stoul(argv[1]),
|
.asset_id = std::stoul(argv[1]),
|
||||||
.type = argv[2],
|
.type = argv[2],
|
||||||
.title = argv[3],
|
.title = argv[3],
|
||||||
|
|
@ -188,44 +214,50 @@ namespace gdpm::cache{
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Check to make sure the directory is there before attempting to open */
|
/* Check to make sure the directory is there before attempting to open */
|
||||||
if(!std::filesystem::exists(cache_path))
|
if(!std::filesystem::exists(params.cache_path))
|
||||||
std::filesystem::create_directories(cache_path);
|
std::filesystem::create_directories(params.cache_path);
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("get_package_info_by_title.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"get_package_info_by_title.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sql{"BEGIN TRANSACTION;"};
|
string sql{"BEGIN TRANSACTION;"};
|
||||||
for(const auto& p_title : package_titles){
|
for(const auto& p_title : package_titles){
|
||||||
sql += "SELECT * FROM " + table_name + " WHERE title='" + p_title + "';";
|
sql += "SELECT * FROM " + params.table_name + " WHERE title='" + p_title + "';";
|
||||||
}
|
}
|
||||||
sql += "COMMIT;";
|
sql += "COMMIT;";
|
||||||
// log::println(sql);
|
// log::println(sql);
|
||||||
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("get_package_info_by_title.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"get_package_info_by_title.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return p_vector;
|
return result_t(p_vector, error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<package_info> get_installed_packages(const std::string& cache_path, const std::string& table_name){
|
result_t<package::info_list> get_installed_packages(const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
std::vector<package_info> p_vector;
|
package::info_list p_vector;
|
||||||
std::string sql{"BEGIN TRANSACTION;"};
|
string sql{"BEGIN TRANSACTION;"};
|
||||||
|
|
||||||
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
auto callback = [](void *data, int argc, char **argv, char **colnames){
|
||||||
std::vector<package_info> *_p_vector = (std::vector<package_info>*) data;
|
package::info_list *_p_vector = (package::info_list*) data;
|
||||||
package_info p{
|
package::info p{
|
||||||
.asset_id = std::stoul(argv[1]),
|
.asset_id = std::stoul(argv[1]),
|
||||||
.type = argv[2],
|
.type = argv[2],
|
||||||
.title = argv[3],
|
.title = argv[3],
|
||||||
|
|
@ -248,46 +280,55 @@ namespace gdpm::cache{
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("get_installed_packages.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"get_installed_packages.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sql += "SELECT * FROM " + table_name + " WHERE is_installed=1; COMMIT;";
|
sql += "SELECT * FROM " + params.table_name + " WHERE is_installed=1; COMMIT;";
|
||||||
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), callback, (void*)&p_vector, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("get_installed_packages.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"get_installed_packages.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return {};
|
return result_t(package::info_list(), error);
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return p_vector;
|
return result_t(p_vector, error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int update_package_info(const std::vector<package_info>& packages, const std::string& cache_path, const std::string& table_name){
|
error update_package_info(const package::info_list& packages, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("update_package_info.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"update_package_info.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sql;
|
string sql;
|
||||||
for(const auto& p : packages){
|
for(const auto& p : packages){
|
||||||
sql += "UPDATE " + table_name + " SET "
|
sql += "UPDATE " + params.table_name + " SET "
|
||||||
" asset_id=" + fmt::to_string(p.asset_id) + ", "
|
" asset_id=" + std::to_string(p.asset_id) + ", "
|
||||||
" type='" + p.type + "', "
|
" type='" + p.type + "', "
|
||||||
" title='" + p.title + "', "
|
" title='" + p.title + "', "
|
||||||
" author='" + p.author + "', " +
|
" author='" + p.author + "', " +
|
||||||
" author_id=" + fmt::to_string(p.author_id) + ", "
|
" author_id=" + std::to_string(p.author_id) + ", "
|
||||||
" version='" + p.version + "', " +
|
" version='" + p.version + "', " +
|
||||||
" godot_version='" + p.godot_version + "', " +
|
" godot_version='" + p.godot_version + "', " +
|
||||||
" cost='" + p.cost + "', " +
|
" cost='" + p.cost + "', " +
|
||||||
|
|
@ -298,115 +339,137 @@ namespace gdpm::cache{
|
||||||
" remote_source='" + p.remote_source + "', " +
|
" remote_source='" + p.remote_source + "', " +
|
||||||
" download_url='" + p.download_url + "', " +
|
" download_url='" + p.download_url + "', " +
|
||||||
" download_hash='" + p.download_hash + "', " +
|
" download_hash='" + p.download_hash + "', " +
|
||||||
" is_installed=" + fmt::to_string(p.is_installed) + ", "
|
" is_installed=" + std::to_string(p.is_installed) + ", "
|
||||||
" install_path='" + p.install_path + "'"
|
" install_path='" + p.install_path + "'"
|
||||||
// " dependencies='" + p.dependencies + "'"
|
// " dependencies='" + p.dependencies + "'"
|
||||||
" WHERE title='" + p.title + "' AND asset_id=" + fmt::to_string(p.asset_id)
|
" WHERE title='" + p.title + "' AND asset_id=" + std::to_string(p.asset_id)
|
||||||
+ ";\n";
|
+ ";\n";
|
||||||
}
|
}
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("update_package_info.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"update_package_info.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int delete_packages(const std::vector<std::string>& package_titles, const std::string& cache_path, const std::string& table_name){
|
error delete_packages(const package::title_list& package_titles, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
std::string sql;
|
string sql;
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("delete_packages.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"delete_packages.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const auto& p_title : package_titles){
|
for(const auto& p_title : package_titles){
|
||||||
sql += "DELETE FROM " + table_name + " WHERE title='"
|
sql += "DELETE FROM " + params.table_name + " WHERE title='"
|
||||||
+ p_title + "';\n";
|
+ p_title + "';\n";
|
||||||
}
|
}
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("delete_packages.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"delete_packages.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int delete_packages(const std::vector<size_t>& package_ids, const std::string& cache_path, const std::string& table_name){
|
error delete_packages(const package::id_list& package_ids, const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
std::string sql;
|
string sql;
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("delete_packages.sqlite3_open(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"delete_packages.sqlite3_open(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const auto& p_id : package_ids){
|
for(const auto& p_id : package_ids){
|
||||||
sql += "DELETE FROM " + table_name + " WHERE asset_id="
|
sql += "DELETE FROM " + params.table_name + " WHERE asset_id="
|
||||||
+ fmt::to_string(p_id) + ";\n";
|
+ std::to_string(p_id) + ";\n";
|
||||||
}
|
}
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("delete_packages.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"delete_packages.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int drop_package_database(const std::string& cache_path, const std::string& table_name){
|
error drop_package_database(const params& params){
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
sqlite3_stmt *res;
|
sqlite3_stmt *res;
|
||||||
char *errmsg = nullptr;
|
char *errmsg = nullptr;
|
||||||
std::string sql{"DROP TABLE IF EXISTS " + table_name + ";\n"};
|
string sql{"DROP TABLE IF EXISTS " + params.table_name + ";\n"};
|
||||||
|
|
||||||
int rc = sqlite3_open(cache_path.c_str(), &db);
|
int rc = sqlite3_open(params.cache_path.c_str(), &db);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("drop_package_database.sqlite3_open(): {}", sqlite3_errmsg(db));
|
error error(rc, std::format(
|
||||||
|
"drop_package_database.sqlite3_open(): {}", sqlite3_errmsg(db)
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errmsg);
|
||||||
if(rc != SQLITE_OK){
|
if(rc != SQLITE_OK){
|
||||||
log::error("drop_package_database.sqlite3_exec(): {}", errmsg);
|
error error(rc, std::format(
|
||||||
|
"drop_package_database.sqlite3_exec(): {}", errmsg
|
||||||
|
));
|
||||||
|
log::error(error);
|
||||||
sqlite3_free(errmsg);
|
sqlite3_free(errmsg);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return rc;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_values(const package_info& p){
|
|
||||||
std::string p_values{};
|
|
||||||
std::string p_title = p.title; /* need copy for utils::replace_all() */
|
|
||||||
|
|
||||||
p_values += fmt::to_string(p.asset_id) + ", ";
|
result_t<string> to_values(const package::info& p){
|
||||||
|
string p_values{};
|
||||||
|
string p_title = p.title; /* need copy for utils::replace_all() */
|
||||||
|
|
||||||
|
p_values += std::to_string(p.asset_id) + ", ";
|
||||||
p_values += "'" + p.type + "', ";
|
p_values += "'" + p.type + "', ";
|
||||||
p_values += "'" + utils::replace_all(p_title, "'", "''") + "', ";
|
p_values += "'" + utils::replace_all(p_title, "'", "''") + "', ";
|
||||||
p_values += "'" + p.author + "', ";
|
p_values += "'" + p.author + "', ";
|
||||||
p_values += fmt::to_string(p.author_id) + ", ";
|
p_values += std::to_string(p.author_id) + ", ";
|
||||||
p_values += "'" + p.version + "', ";
|
p_values += "'" + p.version + "', ";
|
||||||
p_values += "'" + p.godot_version + "', ";
|
p_values += "'" + p.godot_version + "', ";
|
||||||
p_values += "'" + p.cost + "', ";
|
p_values += "'" + p.cost + "', ";
|
||||||
|
|
@ -417,16 +480,17 @@ namespace gdpm::cache{
|
||||||
p_values += "'" + p.remote_source + "', ";
|
p_values += "'" + p.remote_source + "', ";
|
||||||
p_values += "'" + p.download_url + "', ";
|
p_values += "'" + p.download_url + "', ";
|
||||||
p_values += "'" + p.download_hash + "', ";
|
p_values += "'" + p.download_hash + "', ";
|
||||||
p_values += fmt::to_string(p.is_installed) + ", ";
|
p_values += std::to_string(p.is_installed) + ", ";
|
||||||
p_values += "'" + p.install_path + "'";
|
p_values += "'" + p.install_path + "'";
|
||||||
return p_values;
|
return result_t(p_values, error());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_values(const std::vector<package_info>& packages){
|
|
||||||
std::string o;
|
result_t<string> to_values(const package::info_list& packages){
|
||||||
|
string o;
|
||||||
for(const auto& p : packages)
|
for(const auto& p : packages)
|
||||||
o += to_values(p);
|
o += to_values(p).unwrap_unsafe();
|
||||||
return o;
|
return result_t(o, error());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
115
src/config.cpp
115
src/config.cpp
|
|
@ -1,7 +1,9 @@
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
|
||||||
// RapidJSON
|
// RapidJSON
|
||||||
#include <rapidjson/ostreamwrapper.h>
|
#include <rapidjson/ostreamwrapper.h>
|
||||||
|
|
@ -23,6 +25,8 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
namespace gdpm::config{
|
namespace gdpm::config{
|
||||||
|
|
@ -38,8 +42,19 @@ namespace gdpm::config{
|
||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto _build_json_object = [](const string_map& m){
|
||||||
|
string o{"{"};
|
||||||
|
std::for_each(m.begin(), m.end(), [&o](const string_pair& p){
|
||||||
|
o += std::format("\n\"{}\": \"{}\",", p.first, p.second);
|
||||||
|
});
|
||||||
|
if(o.back() == ',')
|
||||||
|
o.pop_back();
|
||||||
|
o += "}";
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
/* Build a JSON string to pass to document */
|
/* Build a JSON string to pass to document */
|
||||||
std::string json{
|
string json{
|
||||||
"{\"username\":\"" + params.username + "\","
|
"{\"username\":\"" + params.username + "\","
|
||||||
+ "\"password\":\"" + params.password + "\","
|
+ "\"password\":\"" + params.password + "\","
|
||||||
+ "\"path\":\"" + params.path + "\","
|
+ "\"path\":\"" + params.path + "\","
|
||||||
|
|
@ -47,7 +62,7 @@ namespace gdpm::config{
|
||||||
+ "\"godot_version\":\"" + params.godot_version + "\","
|
+ "\"godot_version\":\"" + params.godot_version + "\","
|
||||||
+ "\"packages_dir\":\"" + params.packages_dir + "\","
|
+ "\"packages_dir\":\"" + params.packages_dir + "\","
|
||||||
+ "\"tmp_dir\":\"" + params.tmp_dir + "\","
|
+ "\"tmp_dir\":\"" + params.tmp_dir + "\","
|
||||||
+ "\"remote_sources\":" + _build_json_array(params.remote_sources) + ","
|
+ "\"remote_sources\":" + _build_json_object(params.remote_sources) + ","
|
||||||
+ "\"threads\":" + fmt::to_string(params.threads) + ","
|
+ "\"threads\":" + fmt::to_string(params.threads) + ","
|
||||||
+ "\"timeout\":" + fmt::to_string(params.timeout) + ","
|
+ "\"timeout\":" + fmt::to_string(params.timeout) + ","
|
||||||
+ "\"enable_sync\":" + fmt::to_string(params.enable_sync) + ","
|
+ "\"enable_sync\":" + fmt::to_string(params.enable_sync) + ","
|
||||||
|
|
@ -58,17 +73,19 @@ namespace gdpm::config{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gdpm::error load(std::filesystem::path path, context& config, int verbose){
|
error load(
|
||||||
|
std::filesystem::path path,
|
||||||
|
context& config,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
std::fstream file;
|
std::fstream file;
|
||||||
gdpm::error error;
|
|
||||||
file.open(path, std::ios::in);
|
file.open(path, std::ios::in);
|
||||||
if(!file){
|
if(!file){
|
||||||
if(verbose){
|
if(verbose)
|
||||||
log::info("No configuration file found. Creating a new one.");
|
log::info("No configuration file found. Creating a new one.");
|
||||||
config = make_context();
|
config = make_context();
|
||||||
save(config.path, config, verbose);
|
save(config.path, config, verbose);
|
||||||
}
|
return error();
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
else if(file.is_open()){
|
else if(file.is_open()){
|
||||||
/*
|
/*
|
||||||
|
|
@ -79,24 +96,30 @@ namespace gdpm::config{
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
|
|
||||||
/* Read JSON from config, parse, and check document. Must make sure that program does not crash here and use default config instead! */
|
/* Read JSON from config, parse, and check document. Must make sure that program does not crash here and use default config instead! */
|
||||||
std::string contents, line;
|
string contents, line;
|
||||||
while(std::getline(file, line))
|
while(std::getline(file, line))
|
||||||
contents += line + "\n";
|
contents += line + "\n";
|
||||||
|
|
||||||
if(verbose > 0)
|
if(verbose > 0)
|
||||||
log::info("Load config...\n{}", contents.c_str());
|
log::info("Loading configuration file...\n{}", contents.c_str());
|
||||||
|
|
||||||
Document doc;
|
Document doc;
|
||||||
ParseErrorCode status = doc.Parse(contents.c_str()).GetParseError();
|
ParseErrorCode status = doc.Parse(contents.c_str()).GetParseError();
|
||||||
|
|
||||||
if(!doc.IsObject()){
|
if(!doc.IsObject()){
|
||||||
log::error("Could not load config file.");
|
error error(
|
||||||
|
constants::error::FILE_NOT_FOUND,
|
||||||
|
"Could not load config file."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(doc.IsObject());
|
error error = validate(doc);
|
||||||
assert(doc.HasMember("remote_sources"));
|
if(error()){
|
||||||
assert(doc["remote_sources"].IsArray());
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure contents were read correctly. */
|
/* Make sure contents were read correctly. */
|
||||||
// if(!status){
|
// if(!status){
|
||||||
|
|
@ -105,16 +128,23 @@ namespace gdpm::config{
|
||||||
// return context();
|
// return context();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/* Must check if keys exists first, then populate _config_params. */
|
/* Must check if keys exists first, then populate `_config_params`. */
|
||||||
if(doc.HasMember("remote_sources")){
|
if(doc.HasMember("remote_sources")){
|
||||||
if(doc["remote_sources"].IsArray()){
|
if(doc["remote_sources"].IsArray()){
|
||||||
const Value& srcs = doc["remote_sources"];
|
const Value& srcs = doc["remote_sources"];
|
||||||
for(auto& src : srcs.GetArray()){
|
for(auto& src : srcs.GetObject()){
|
||||||
// config.remote_sources.push_back(src.GetString());
|
// config.remote_sources.push_back(src.GetString());
|
||||||
config.remote_sources.insert(src.GetString());
|
config.remote_sources.insert(
|
||||||
|
std::pair(src.name.GetString(), src.value.GetString())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
log::error("Malformed sources found.");
|
gdpm::error error(
|
||||||
|
constants::error::INVALID_KEY,
|
||||||
|
"Could not read key `remote_sources`."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto _get_value_string = [](Document& doc, const char *property){
|
auto _get_value_string = [](Document& doc, const char *property){
|
||||||
|
|
@ -141,17 +171,21 @@ namespace gdpm::config{
|
||||||
config.enable_sync = _get_value_int(doc, "enable_sync");
|
config.enable_sync = _get_value_int(doc, "enable_sync");
|
||||||
config.enable_file_logging = _get_value_int(doc, "enable_file_logging");
|
config.enable_file_logging = _get_value_int(doc, "enable_file_logging");
|
||||||
}
|
}
|
||||||
return error;
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gdpm::error save(std::filesystem::path path, const context& config, int verbose){
|
error save(
|
||||||
|
std::filesystem::path path,
|
||||||
|
const context& config,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
|
|
||||||
/* Build a JSON string to pass to document */
|
/* Build a JSON string to pass to document */
|
||||||
std::string json = to_json(config);
|
string json = to_json(config);
|
||||||
if(verbose > 0)
|
if(verbose > 0)
|
||||||
log::info("Save config...\n{}", json.c_str());
|
log::info("Saving configuration file...\n{}", json.c_str());
|
||||||
|
|
||||||
/* Dump JSON config to file */
|
/* Dump JSON config to file */
|
||||||
Document doc;
|
Document doc;
|
||||||
|
|
@ -166,14 +200,28 @@ namespace gdpm::config{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
context make_context(const std::string& username, const std::string& password, const std::string& path, const std::string& token, const std::string& godot_version, const std::string& packages_dir, const std::string& tmp_dir, const std::set<std::string>& remote_sources, size_t threads, size_t timeout, bool enable_sync, bool enable_file_logging, int verbose){
|
context make_context(
|
||||||
|
const string& username,
|
||||||
|
const string& password,
|
||||||
|
const string& path,
|
||||||
|
const string& token,
|
||||||
|
const string& godot_version,
|
||||||
|
const string& packages_dir,
|
||||||
|
const string& tmp_dir,
|
||||||
|
const string_map& remote_sources,
|
||||||
|
size_t threads,
|
||||||
|
size_t timeout,
|
||||||
|
bool enable_sync,
|
||||||
|
bool enable_file_logging,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
context config {
|
context config {
|
||||||
.username = username,
|
.username = username,
|
||||||
.password = password,
|
.password = password,
|
||||||
.path = path,
|
.path = path,
|
||||||
.token = token,
|
.token = token,
|
||||||
.godot_version = godot_version,
|
.godot_version = godot_version,
|
||||||
.packages_dir = (packages_dir.empty()) ? std::string(getenv("HOME")) + ".gdpm" : packages_dir,
|
.packages_dir = (packages_dir.empty()) ? string(getenv("HOME")) + ".gdpm" : packages_dir,
|
||||||
.tmp_dir = tmp_dir,
|
.tmp_dir = tmp_dir,
|
||||||
.remote_sources = remote_sources,
|
.remote_sources = remote_sources,
|
||||||
.threads = threads,
|
.threads = threads,
|
||||||
|
|
@ -185,4 +233,23 @@ namespace gdpm::config{
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error validate(const rapidjson::Document& doc){
|
||||||
|
error error(constants::error::INVALID_CONFIG, "");
|
||||||
|
if(!doc.IsObject()){
|
||||||
|
error.set_message("Document is not a JSON object.");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
if(!doc.HasMember("remote_sources")){
|
||||||
|
error.set_message("Could not find `remote_sources` in config.");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
if(!doc["remote_sources"].IsObject()){
|
||||||
|
error.set_message("Key `remote_sources` is not a JSON object.");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error.set_code(constants::error::NONE);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
41
src/http.cpp
41
src/http.cpp
|
|
@ -8,7 +8,20 @@
|
||||||
|
|
||||||
|
|
||||||
namespace gdpm::http{
|
namespace gdpm::http{
|
||||||
response request_get(const std::string& url, size_t timeout, int verbose){
|
|
||||||
|
string url_escape(const string &url){
|
||||||
|
CURL *curl = nullptr;
|
||||||
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
char *escaped_url = curl_easy_escape(curl, url.c_str(), url.size());
|
||||||
|
std::string url_copy = escaped_url;
|
||||||
|
curl_global_cleanup();
|
||||||
|
return escaped_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
response request_get(
|
||||||
|
const string& url,
|
||||||
|
const http::params& params
|
||||||
|
){
|
||||||
CURL *curl = nullptr;
|
CURL *curl = nullptr;
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
utils::memory_buffer buf = utils::make_buffer();
|
utils::memory_buffer buf = utils::make_buffer();
|
||||||
|
|
@ -28,10 +41,10 @@ namespace gdpm::http{
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, params.timeout);
|
||||||
res = curl_easy_perform(curl);
|
res = curl_easy_perform(curl);
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
||||||
if(res != CURLE_OK && verbose > 0)
|
if(res != CURLE_OK && params.verbose > 0)
|
||||||
log::error("_make_request.curl_easy_perform(): {}", curl_easy_strerror(res));
|
log::error("_make_request.curl_easy_perform(): {}", curl_easy_strerror(res));
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +55,12 @@ namespace gdpm::http{
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
response request_post(const std::string& url, const char *post_fields, size_t timeout, int verbose){
|
|
||||||
|
response request_post(
|
||||||
|
const string& url,
|
||||||
|
const char *post_fields,
|
||||||
|
const http::params& params
|
||||||
|
){
|
||||||
CURL *curl = nullptr;
|
CURL *curl = nullptr;
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
utils::memory_buffer buf = utils::make_buffer();
|
utils::memory_buffer buf = utils::make_buffer();
|
||||||
|
|
@ -62,10 +80,10 @@ namespace gdpm::http{
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&buf);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_buffer);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, params.timeout);
|
||||||
res = curl_easy_perform(curl);
|
res = curl_easy_perform(curl);
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
||||||
if(res != CURLE_OK && verbose > 0)
|
if(res != CURLE_OK && params.verbose > 0)
|
||||||
log::error("_make_request.curl_easy_perform(): {}", curl_easy_strerror(res));
|
log::error("_make_request.curl_easy_perform(): {}", curl_easy_strerror(res));
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +94,12 @@ namespace gdpm::http{
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
response download_file(const std::string& url, const std::string& storage_path, size_t timeout, int verbose){
|
|
||||||
|
response download_file(
|
||||||
|
const string& url,
|
||||||
|
const string& storage_path,
|
||||||
|
const http::params& params
|
||||||
|
){
|
||||||
CURL *curl = nullptr;
|
CURL *curl = nullptr;
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
response r;
|
response r;
|
||||||
|
|
@ -110,10 +133,10 @@ namespace gdpm::http{
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_stream);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::curl_write_to_stream);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, constants::UserAgent.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, params.timeout);
|
||||||
res = curl_easy_perform(curl);
|
res = curl_easy_perform(curl);
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r.code);
|
||||||
if(res != CURLE_OK && verbose > 0){
|
if(res != CURLE_OK && params.verbose > 0){
|
||||||
log::error("download_file.curl_easy_perform() failed: {}", curl_easy_strerror(res));
|
log::error("download_file.curl_easy_perform() failed: {}", curl_easy_strerror(res));
|
||||||
}
|
}
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,14 @@
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "package_manager.hpp"
|
#include "package_manager.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv){
|
||||||
gdpm::package_manager::initialize(argc, argv);
|
using namespace gdpm;
|
||||||
gdpm::package_manager::execute();
|
result_t <package_manager::exec_args> r_input = package_manager::initialize(argc, argv);
|
||||||
gdpm::package_manager::finalize();
|
package_manager::exec_args input = r_input.unwrap_unsafe();
|
||||||
|
package_manager::execute(input.args, input.opts);
|
||||||
|
package_manager::finalize();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
763
src/package.cpp
Normal file
763
src/package.cpp
Normal file
|
|
@ -0,0 +1,763 @@
|
||||||
|
|
||||||
|
#include "package.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "rest_api.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "cache.hpp"
|
||||||
|
#include "http.hpp"
|
||||||
|
#include "remote.hpp"
|
||||||
|
#include <future>
|
||||||
|
#include <rapidjson/ostreamwrapper.h>
|
||||||
|
#include <rapidjson/prettywriter.h>
|
||||||
|
|
||||||
|
namespace gdpm::package{
|
||||||
|
|
||||||
|
error install(
|
||||||
|
const config::context& config,
|
||||||
|
const package::title_list& package_titles,
|
||||||
|
const package::params& params
|
||||||
|
){
|
||||||
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
/* TODO: Need a way to use remote sources from config until none left */
|
||||||
|
|
||||||
|
/* Check if the package data is already stored in cache. If it is, there
|
||||||
|
is no need to do a lookup to synchronize the local database since we
|
||||||
|
have all the information we need to fetch the asset data. */
|
||||||
|
result_t result = cache::get_package_info_by_title(package_titles);
|
||||||
|
package::info_list p_found = {};
|
||||||
|
package::info_list p_cache = result.unwrap_unsafe();
|
||||||
|
|
||||||
|
/* Synchronize database information and then try to get data again from
|
||||||
|
cache if possible. */
|
||||||
|
if(config.enable_sync){
|
||||||
|
if(p_cache.empty()){
|
||||||
|
result_t result = synchronize_database(config, package_titles);
|
||||||
|
p_cache = result.unwrap_unsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& p_title : package_titles){
|
||||||
|
auto found = std::find_if(
|
||||||
|
p_cache.begin(),
|
||||||
|
p_cache.end(),
|
||||||
|
[&p_title](const package::info& p){
|
||||||
|
return p.title == p_title;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if(found != p_cache.end()){
|
||||||
|
p_found.emplace_back(*found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Found nothing to install so there's nothing to do at this point. */
|
||||||
|
if(p_found.empty()){
|
||||||
|
constexpr const char *message = "No packages found to install.";
|
||||||
|
log::error(message);
|
||||||
|
return error(constants::error::NOT_FOUND, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::println("Packages to install: ");
|
||||||
|
for(const auto& p : p_found){
|
||||||
|
std::string output((p.is_installed) ? p.title + " (reinstall)" : p.title);
|
||||||
|
log::print(" {} ", (p.is_installed) ? p.title + " (reinstall)" : p.title);
|
||||||
|
}
|
||||||
|
log::println("");
|
||||||
|
|
||||||
|
if(!params.skip_prompt){
|
||||||
|
if(!utils::prompt_user_yn("Do you want to install these packages? (y/n)"))
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try and obtain all requested packages. */
|
||||||
|
using ss_pair = std::pair<std::string, std::string>;
|
||||||
|
std::vector<ss_pair> dir_pairs;
|
||||||
|
std::vector<std::future<error>> tasks;
|
||||||
|
rest_api::context rest_api_params = rest_api::make_from_config(config);
|
||||||
|
for(auto& p : p_found){ // TODO: Execute each in parallel using coroutines??
|
||||||
|
|
||||||
|
/* Check if a remote source was provided. If not, then try to get packages
|
||||||
|
in global storage location only. */
|
||||||
|
|
||||||
|
log::info("Fetching asset data for \"{}\"...", p.title);
|
||||||
|
std::string url{config.remote_sources.at(params.remote_source) + rest_api::endpoints::GET_AssetId};
|
||||||
|
std::string package_dir, tmp_dir, tmp_zip;
|
||||||
|
|
||||||
|
/* Retrieve necessary asset data if it was found already in cache */
|
||||||
|
Document doc;
|
||||||
|
bool is_valid = p.download_url.empty() || p.category.empty() || p.description.empty() || p.support_level.empty();
|
||||||
|
if(is_valid){
|
||||||
|
doc = rest_api::get_asset(url, p.asset_id, rest_api_params);
|
||||||
|
if(doc.HasParseError() || doc.IsNull()){
|
||||||
|
constexpr const char *message = "\nError parsing HTTP response.";
|
||||||
|
log::error(message);
|
||||||
|
return error(doc.GetParseError(), message);
|
||||||
|
}
|
||||||
|
p.category = doc["category"].GetString();
|
||||||
|
p.description = doc["description"].GetString();
|
||||||
|
p.support_level = doc["support_level"].GetString();
|
||||||
|
p.download_url = doc["download_url"].GetString();
|
||||||
|
p.download_hash = doc["download_hash"].GetString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
log::error("Not a valid package.");
|
||||||
|
/* Package for in cache so no remote request. Still need to populate RapidJson::Document to write to package.json.
|
||||||
|
NOTE: This may not be necessary at all!
|
||||||
|
*/
|
||||||
|
// doc["asset_id"].SetUint64(p.asset_id
|
||||||
|
// doc["type"].SetString(p.type, doc.GetAllocator());
|
||||||
|
// doc["title"].SetString(p.title, doc.GetAllocator());
|
||||||
|
// doc["author"].SetString(p.author, doc.GetAllocator());
|
||||||
|
// doc["author_id"].SetUint64(p.author_id);
|
||||||
|
// doc["version"].SetString(p.version, doc.GetAllocator());
|
||||||
|
// doc["category"].SetString(p.category, doc.GetAllocator());
|
||||||
|
// doc["godot_version"].SetString(p.godot_version, doc.GetAllocator());
|
||||||
|
// doc["cost"].SetString(p.cost, doc.GetAllocator());
|
||||||
|
// doc["description"].SetString(p.description, doc.GetAllocator());
|
||||||
|
// doc["support_level"].SetString(p.support_level, doc.GetAllocator());
|
||||||
|
// doc["download_url"].SetString(p.download_url, doc.GetAllocator());
|
||||||
|
// doc["download_hash"].SetString(p.download_hash, doc.GetAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
/* Make directories for packages if they don't exist to keep everything organized */
|
||||||
|
if(!std::filesystem::exists(config.tmp_dir))
|
||||||
|
std::filesystem::create_directories(config.tmp_dir);
|
||||||
|
if(!std::filesystem::exists(config.packages_dir))
|
||||||
|
std::filesystem::create_directories(config.packages_dir);
|
||||||
|
|
||||||
|
/* Dump asset information for lookup into JSON in package directory */
|
||||||
|
if(!std::filesystem::exists(package_dir))
|
||||||
|
std::filesystem::create_directory(package_dir);
|
||||||
|
|
||||||
|
std::ofstream ofs(package_dir + "/asset.json");
|
||||||
|
OStreamWrapper osw(ofs);
|
||||||
|
PrettyWriter<OStreamWrapper> writer(osw);
|
||||||
|
doc.Accept(writer);
|
||||||
|
|
||||||
|
/* Check if we already have a stored temporary file before attempting to download */
|
||||||
|
if(std::filesystem::exists(tmp_zip) && std::filesystem::is_regular_file(tmp_zip)){
|
||||||
|
log::println("Found cached package. Skipping download.", p.title);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
/* Download all the package files and place them in tmp directory. */
|
||||||
|
log::info_n("Downloading \"{}\"...", p.title);
|
||||||
|
std::string download_url = p.download_url;// doc["download_url"].GetString();
|
||||||
|
std::string title = p.title;// doc["title"].GetString();
|
||||||
|
http::response response = http::download_file(download_url, tmp_zip);
|
||||||
|
if(response.code == http::OK){
|
||||||
|
log::println("Done.");
|
||||||
|
}else{
|
||||||
|
error error(
|
||||||
|
constants::error::HTTP_RESPONSE_ERROR,
|
||||||
|
std::format("HTTP Error: {}", response.code)
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_pairs.emplace_back(ss_pair(tmp_zip, package_dir + "/"));
|
||||||
|
|
||||||
|
p.is_installed = true;
|
||||||
|
p.install_path = package_dir;
|
||||||
|
|
||||||
|
/* Extract all the downloaded packages to their appropriate directory location. */
|
||||||
|
for(const auto& p : dir_pairs)
|
||||||
|
utils::extract_zip(p.first.c_str(), p.second.c_str());
|
||||||
|
|
||||||
|
/* Update the cache data with information from */
|
||||||
|
log::info_n("Updating local asset data...");
|
||||||
|
cache::update_package_info(p_found);
|
||||||
|
log::println("done.");
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error remove(
|
||||||
|
const config::context& config,
|
||||||
|
const string_list& package_titles,
|
||||||
|
const package::params& params
|
||||||
|
){
|
||||||
|
using namespace rapidjson;
|
||||||
|
using namespace std::filesystem;
|
||||||
|
|
||||||
|
/* Find the packages to remove if they're is_installed and show them to the user */
|
||||||
|
result_t result = cache::get_package_info_by_title(package_titles);
|
||||||
|
std::vector<package::info> p_cache = result.unwrap_unsafe();
|
||||||
|
if(p_cache.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::NOT_FOUND,
|
||||||
|
"\nCould not find any packages to remove."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Count number packages in cache flagged as is_installed. If there are none, then there's nothing to do. */
|
||||||
|
size_t p_count = 0;
|
||||||
|
std::for_each(p_cache.begin(), p_cache.end(), [&p_count](const package::info& p){
|
||||||
|
p_count += (p.is_installed) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(p_count == 0){
|
||||||
|
error error(
|
||||||
|
constants::error::NOT_FOUND,
|
||||||
|
"\nNo packages to remove."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::println("Packages to remove:");
|
||||||
|
for(const auto& p : p_cache)
|
||||||
|
if(p.is_installed)
|
||||||
|
log::print(" {} ", p.title);
|
||||||
|
log::println("");
|
||||||
|
|
||||||
|
if(!params.skip_prompt){
|
||||||
|
if(!utils::prompt_user_yn("Do you want to remove these packages? (y/n)"))
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info_n("Removing packages...");
|
||||||
|
for(auto& p : p_cache){
|
||||||
|
const std::filesystem::path path{config.packages_dir};
|
||||||
|
std::filesystem::remove_all(config.packages_dir + "/" + p.title);
|
||||||
|
if(config.verbose > 0){
|
||||||
|
log::debug("package directory: {}", path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Traverse the package directory */
|
||||||
|
// for(const auto& entry : recursive_directory_iterator(path)){
|
||||||
|
// if(entry.is_directory()){
|
||||||
|
// }
|
||||||
|
// else if(entry.is_regular_file()){
|
||||||
|
// std::string filename = entry.path().filename().string();
|
||||||
|
// std::string pkg_path = entry.path().lexically_normal().string();
|
||||||
|
|
||||||
|
// // pkg_path = utils::replace_all(pkg_path, " ", "\\ ");
|
||||||
|
// if(filename == "package.json"){
|
||||||
|
// std::string contents = utils::readfile(pkg_path);
|
||||||
|
// Document doc;
|
||||||
|
// if(config.verbose > 0){
|
||||||
|
// log::debug("package path: {}", pkg_path);
|
||||||
|
// log::debug("contents: \n{}", contents);
|
||||||
|
// }
|
||||||
|
// doc.Parse(contents.c_str());
|
||||||
|
// if(doc.IsNull()){
|
||||||
|
// log::println("");
|
||||||
|
// log::error("Could not remove packages. Parsing 'package.json' returned NULL.");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
p.is_installed = false;
|
||||||
|
}
|
||||||
|
log::println("Done.");
|
||||||
|
log::info_n("Updating local asset data...");
|
||||||
|
cache::update_package_info(p_cache);
|
||||||
|
log::println("done.");
|
||||||
|
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes all local packages.
|
||||||
|
*/
|
||||||
|
error remove_all(
|
||||||
|
const config::context& config,
|
||||||
|
const package::params& params
|
||||||
|
){
|
||||||
|
/* Get the list of all packages to remove then remove */
|
||||||
|
result_t r_installed = cache::get_installed_packages();
|
||||||
|
package::info_list p_installed = r_installed.unwrap_unsafe();
|
||||||
|
result_t r_titles = get_package_titles(p_installed);
|
||||||
|
package::title_list p_titles = r_titles.unwrap_unsafe();
|
||||||
|
return remove(config, p_titles, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error update(
|
||||||
|
const config::context& config,
|
||||||
|
const package::title_list& package_titles,
|
||||||
|
const package::params& params
|
||||||
|
){
|
||||||
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
/* If no package titles provided, update everything and then exit */
|
||||||
|
rest_api::context rest_api_params = rest_api::make_from_config(config);
|
||||||
|
if(package_titles.empty()){
|
||||||
|
std::string url{constants::HostUrl};
|
||||||
|
url += 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.";
|
||||||
|
log::error(message);
|
||||||
|
return error(constants::error::HOST_UNREACHABLE, message);
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fetch remote asset data and compare to see if there are package updates */
|
||||||
|
std::vector<std::string> p_updates = {};
|
||||||
|
result_t r_cache = cache::get_package_info_by_title(package_titles);
|
||||||
|
package::info_list p_cache = r_cache.unwrap_unsafe();
|
||||||
|
|
||||||
|
log::println("Packages to update: ");
|
||||||
|
for(const auto& p_title : p_updates)
|
||||||
|
log::print(" {} ", p_title);
|
||||||
|
log::println("");
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
Document doc = rest_api::get_asset(url, p.asset_id);
|
||||||
|
std::string remote_version = doc["version"].GetString();
|
||||||
|
if(p.version != remote_version){
|
||||||
|
p_updates.emplace_back(p.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!params.skip_prompt){
|
||||||
|
if(!utils::prompt_user_yn("Do you want to update the following packages? (y/n)"))
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
error error;
|
||||||
|
error = remove(config, p_updates);
|
||||||
|
error = install(config, p_updates, params);
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error search(
|
||||||
|
const config::context& config,
|
||||||
|
const package::title_list &package_titles,
|
||||||
|
const package::params& params
|
||||||
|
){
|
||||||
|
result_t r_cache = cache::get_package_info_by_title(package_titles);
|
||||||
|
std::vector<package::info> p_cache = r_cache.unwrap_unsafe();
|
||||||
|
|
||||||
|
if(!p_cache.empty() && !config.enable_sync){
|
||||||
|
print_list(p_cache);
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
rest_api::context 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);
|
||||||
|
rest_api_params.verbose = config.verbose;
|
||||||
|
rest_api_params.godot_version = config.godot_version;
|
||||||
|
rest_api_params.max_results = 200;
|
||||||
|
|
||||||
|
std::string request_url{constants::HostUrl};
|
||||||
|
request_url += rest_api::endpoints::GET_Asset;
|
||||||
|
Document doc = rest_api::get_assets_list(request_url, rest_api_params);
|
||||||
|
if(doc.IsNull()){
|
||||||
|
error error(
|
||||||
|
constants::error::HOST_UNREACHABLE,
|
||||||
|
"Could not fetch metadata."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info("{} package(s) found...", doc["total_items"].GetInt());
|
||||||
|
print_list(doc);
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error list(
|
||||||
|
const config::context& config,
|
||||||
|
const args_t& args,
|
||||||
|
const opts_t& opts
|
||||||
|
){
|
||||||
|
using namespace rapidjson;
|
||||||
|
using namespace std::filesystem;
|
||||||
|
|
||||||
|
if(opts.empty() || opts.contains("packages")){
|
||||||
|
result_t r_installed = cache::get_installed_packages();
|
||||||
|
info_list p_installed = r_installed.unwrap_unsafe();
|
||||||
|
if(!p_installed.empty()){
|
||||||
|
log::println("Installed packages: ");
|
||||||
|
print_list(p_installed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(opts.contains("remote")){
|
||||||
|
remote::print_repositories(config);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
error error(
|
||||||
|
constants::error::UNKNOWN_COMMAND,
|
||||||
|
"Unrecognized subcommand. Try either 'packages' or 'remote' instead."
|
||||||
|
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error export_to(const string_list& paths){
|
||||||
|
/* Get all installed package information for export */
|
||||||
|
result_t r_installed = cache::get_installed_packages();
|
||||||
|
info_list p_installed = r_installed.unwrap_unsafe();
|
||||||
|
|
||||||
|
result_t r_titles = get_package_titles(p_installed);
|
||||||
|
title_list p_titles = r_titles.unwrap_unsafe();
|
||||||
|
|
||||||
|
/* Build string of contents with one package title per line */
|
||||||
|
string output{};
|
||||||
|
std::for_each(p_titles.begin(), p_titles.end(), [&output](const string& p){
|
||||||
|
output += p + "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Write contents of installed packages in reusable format */
|
||||||
|
for(const auto& path : paths ){
|
||||||
|
std::ofstream of(path);
|
||||||
|
if(std::filesystem::exists(path)){
|
||||||
|
constexpr const char *message = "File or directory exists!";
|
||||||
|
log::error(message);
|
||||||
|
of.close();
|
||||||
|
return error(constants::error::FILE_EXISTS, message);
|
||||||
|
}
|
||||||
|
log::println("writing contents to file");
|
||||||
|
of << output;
|
||||||
|
of.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error link(
|
||||||
|
const config::context& config,
|
||||||
|
const title_list& package_titles,
|
||||||
|
const opts_t& opts
|
||||||
|
){
|
||||||
|
using namespace std::filesystem;
|
||||||
|
|
||||||
|
path_list paths = {};
|
||||||
|
if(opts.contains("path")){
|
||||||
|
paths = opts.at("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(paths.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::PATH_NOT_DEFINED,
|
||||||
|
"No path set. Use '--path' option to set a path."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
result_t r_cache = cache::get_package_info_by_title(package_titles);
|
||||||
|
info_list p_found = {};
|
||||||
|
info_list p_cache = r_cache.unwrap_unsafe();
|
||||||
|
if(p_cache.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::NOT_FOUND,
|
||||||
|
"Could not find any packages to link."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& p_title : package_titles){
|
||||||
|
auto found = std::find_if(p_cache.begin(), p_cache.end(), [&p_title](const package::info& p){ return p.title == p_title; });
|
||||||
|
if(found != p_cache.end()){
|
||||||
|
p_found.emplace_back(*found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(p_found.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::NO_PACKAGE_FOUND,
|
||||||
|
"No packages found to link."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the storage paths for all packages to create symlinks */
|
||||||
|
const path package_dir{config.packages_dir};
|
||||||
|
for(const auto& p : p_found){
|
||||||
|
for(const auto& path : paths){
|
||||||
|
log::info_n("Creating symlink for \"{}\" package to '{}'...", p.title, path + "/" + p.title);
|
||||||
|
// std::filesystem::path target{config.packages_dir + "/" + p.title};
|
||||||
|
std::filesystem::path target = {current_path().string() + "/" + config.packages_dir + "/" + p.title};
|
||||||
|
std::filesystem::path symlink_path{path + "/" + p.title};
|
||||||
|
if(!std::filesystem::exists(symlink_path.string()))
|
||||||
|
std::filesystem::create_directories(path + "/");
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directory_symlink(target, symlink_path, ec);
|
||||||
|
if(ec){
|
||||||
|
error error(
|
||||||
|
constants::error::STD_ERROR,
|
||||||
|
std::format("Could not create symlink: {}", ec.message())
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
}
|
||||||
|
log::println("Done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error clone(
|
||||||
|
const config::context& config,
|
||||||
|
const title_list& package_titles,
|
||||||
|
const opts_t& paths
|
||||||
|
){
|
||||||
|
using namespace std::filesystem;
|
||||||
|
|
||||||
|
if(paths.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::PATH_NOT_DEFINED,
|
||||||
|
"No path set. Use '--path' option to set a path."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t r_cache = cache::get_package_info_by_title(package_titles);
|
||||||
|
package::info_list p_found = {};
|
||||||
|
package::info_list p_cache = r_cache.unwrap_unsafe();
|
||||||
|
if(p_cache.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::NO_PACKAGE_FOUND,
|
||||||
|
"Could not find any packages to clone."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& p_title : package_titles){
|
||||||
|
auto found = std::find_if(p_cache.begin(), p_cache.end(), [&p_title](const package::info& p){ return p.title == p_title; });
|
||||||
|
if(found != p_cache.end()){
|
||||||
|
p_found.emplace_back(*found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(p_found.empty()){
|
||||||
|
error error(
|
||||||
|
constants::error::NO_PACKAGE_FOUND,
|
||||||
|
"No packages found to clone."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the storage paths for all packages to create clones */
|
||||||
|
const path package_dir{config.packages_dir};
|
||||||
|
for(const auto& p : p_found){
|
||||||
|
for(const auto& path_list : paths){
|
||||||
|
for(const auto& path : path_list.second){
|
||||||
|
log::info("Cloning \"{}\" package to {}", p.title, path + "/" + p.title);
|
||||||
|
std::filesystem::path from{config.packages_dir + "/" + p.title};
|
||||||
|
std::filesystem::path to{path + "/" + p.title};
|
||||||
|
if(!std::filesystem::exists(to.string()))
|
||||||
|
std::filesystem::create_directories(to);
|
||||||
|
|
||||||
|
/* TODO: Add an option to force overwriting (i.e. --overwrite) */
|
||||||
|
std::filesystem::copy(from, to, copy_options::update_existing | copy_options::recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void print_list(const info_list& packages){
|
||||||
|
for(const auto& p : packages){
|
||||||
|
log::println("{}/{}/{} {} id={}\n\tGodot {}, {}, {}, Last Modified: {}",
|
||||||
|
p.support_level,
|
||||||
|
p.author,
|
||||||
|
p.title,
|
||||||
|
p.version,
|
||||||
|
p.asset_id,
|
||||||
|
p.godot_version,
|
||||||
|
p.cost,
|
||||||
|
p.category,
|
||||||
|
p.modify_date
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void print_list(const rapidjson::Document& json){
|
||||||
|
for(const auto& o : json["result"].GetArray()){
|
||||||
|
log::println("{}/{}/{} {} id={}\n\tGodot {}, {}, {}, Last Modified: {}",
|
||||||
|
o["support_level"] .GetString(),
|
||||||
|
o["author"] .GetString(),
|
||||||
|
o["title"] .GetString(),
|
||||||
|
o["version_string"] .GetString(),
|
||||||
|
o["asset_id"] .GetString(),
|
||||||
|
o["godot_version"] .GetString(),
|
||||||
|
o["cost"] .GetString(),
|
||||||
|
o["category"] .GetString(),
|
||||||
|
o["modify_date"] .GetString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t<info_list> get_package_info(const title_list& package_titles){
|
||||||
|
return cache::get_package_info_by_title(package_titles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t<title_list> get_package_titles(const info_list &packages){
|
||||||
|
title_list package_titles;
|
||||||
|
std::for_each(packages.begin(), packages.end(), [&package_titles](const package::info& p){
|
||||||
|
package_titles.emplace_back(p.title);
|
||||||
|
});
|
||||||
|
return result_t(package_titles, error());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void clean_temporary(
|
||||||
|
const config::context& config,
|
||||||
|
const title_list& package_titles
|
||||||
|
){
|
||||||
|
if(package_titles.empty()){
|
||||||
|
log::info("Cleaned all temporary files.");
|
||||||
|
std::filesystem::remove_all(config.tmp_dir);
|
||||||
|
}
|
||||||
|
/* Find the path of each packages is_installed then delete temporaries */
|
||||||
|
log::info_n("Cleaning temporary files...");
|
||||||
|
for(const auto& p_title : package_titles){
|
||||||
|
string tmp_zip = config.tmp_dir + "/" + p_title + ".zip";
|
||||||
|
if(config.verbose > 0)
|
||||||
|
log::info("Removed '{}'", tmp_zip);
|
||||||
|
std::filesystem::remove_all(tmp_zip);
|
||||||
|
}
|
||||||
|
log::println("Done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t<info_list> synchronize_database(
|
||||||
|
const config::context& config,
|
||||||
|
const title_list& package_titles
|
||||||
|
){
|
||||||
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
rest_api::context rest_api_params = rest_api::make_from_config(config);
|
||||||
|
rest_api_params.page = 0;
|
||||||
|
int page = 0;
|
||||||
|
int page_length = 0;
|
||||||
|
// int total_pages = 0;
|
||||||
|
int total_items = 0;
|
||||||
|
int items_left = 0;
|
||||||
|
|
||||||
|
log::info("Sychronizing database...");
|
||||||
|
do{
|
||||||
|
/* Make the GET request to get page data and store it in the local
|
||||||
|
package database. Also, check to see if we need to keep going. */
|
||||||
|
std::string url{constants::HostUrl};
|
||||||
|
url += rest_api::endpoints::GET_Asset;
|
||||||
|
Document doc = rest_api::get_assets_list(url, rest_api_params);
|
||||||
|
rest_api_params.page += 1;
|
||||||
|
|
||||||
|
if(doc.IsNull()){
|
||||||
|
error error(
|
||||||
|
constants::error::EMPTY_RESPONSE,
|
||||||
|
"Could not get response from server. Aborting."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return result_t(info_list(), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Need to know how many pages left to get and how many we get per
|
||||||
|
request. */
|
||||||
|
page = doc["page"].GetInt();
|
||||||
|
page_length = doc["page_length"].GetInt();
|
||||||
|
// total_pages = doc["pages"].GetInt();
|
||||||
|
total_items = doc["total_items"].GetInt();
|
||||||
|
items_left = total_items - (page + 1) * page_length;
|
||||||
|
|
||||||
|
// log::info("page: {}, page length: {}, total pages: {}, total items: {}, items left: {}", page, page_length, total_pages, total_items, items_left);
|
||||||
|
|
||||||
|
if(page == 0){
|
||||||
|
error error;
|
||||||
|
error = cache::drop_package_database();
|
||||||
|
error = cache::create_package_database();
|
||||||
|
}
|
||||||
|
|
||||||
|
info_list packages;
|
||||||
|
for(const auto& o : doc["result"].GetArray()){
|
||||||
|
// log::println("=======================");
|
||||||
|
info p{
|
||||||
|
.asset_id = std::stoul(o["asset_id"].GetString()),
|
||||||
|
.title = o["title"].GetString(),
|
||||||
|
.author = o["author"].GetString(),
|
||||||
|
.author_id = std::stoul(o["author_id"].GetString()),
|
||||||
|
.version = o["version"].GetString(),
|
||||||
|
.godot_version = o["godot_version"].GetString(),
|
||||||
|
.cost = o["cost"].GetString(),
|
||||||
|
.modify_date = o["modify_date"].GetString(),
|
||||||
|
.category = o["category"].GetString(),
|
||||||
|
.remote_source = url
|
||||||
|
};
|
||||||
|
packages.emplace_back(p);
|
||||||
|
}
|
||||||
|
error error = cache::insert_package_info(packages);
|
||||||
|
if (error.has_occurred()){
|
||||||
|
log::error(error);
|
||||||
|
/* FIXME: Should this stop here or keep going? */
|
||||||
|
}
|
||||||
|
/* Make the same request again to get the rest of the needed data
|
||||||
|
using the same request, but with a different page, then update
|
||||||
|
variables as needed. */
|
||||||
|
|
||||||
|
|
||||||
|
} while(items_left > 0);
|
||||||
|
|
||||||
|
log::println("Done.");
|
||||||
|
|
||||||
|
return cache::get_package_info_by_title(package_titles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t<info_list> resolve_dependencies(
|
||||||
|
const config::context& config,
|
||||||
|
const title_list& package_titles
|
||||||
|
){
|
||||||
|
result_t r_cache = cache::get_package_info_by_title(package_titles);
|
||||||
|
info_list p_cache = r_cache.unwrap_unsafe();
|
||||||
|
info_list p_deps = {};
|
||||||
|
|
||||||
|
/* Build an graph of every thing to check then install in order */
|
||||||
|
for(const auto& p : p_cache){
|
||||||
|
if(p.dependencies.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Check if dependency has a dependency. If so, resolve those first. */
|
||||||
|
for(const auto& d : p.dependencies){
|
||||||
|
result_t r_temp = resolve_dependencies(config, {d.title});
|
||||||
|
info_list temp = r_temp.unwrap_unsafe();
|
||||||
|
utils::move_if_not(temp, p_deps, [](const info& p){ return true; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_t(p_deps, error());
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
103
src/remote.cpp
Normal file
103
src/remote.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
|
||||||
|
#include "remote.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <readline/readline.h>
|
||||||
|
|
||||||
|
namespace gdpm::remote{
|
||||||
|
error _handle_remote(
|
||||||
|
config::context& config,
|
||||||
|
const args_t& args,
|
||||||
|
const opts_t& opts
|
||||||
|
){
|
||||||
|
log::println("_handle_remote");
|
||||||
|
for(const auto& arg : args){
|
||||||
|
log::println("arg: {}", arg);
|
||||||
|
}
|
||||||
|
for(const auto& opt : opts){
|
||||||
|
log::println("opt: {}:{}", opt.first, utils::join(opt.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if enough arguments are supplied */
|
||||||
|
size_t argc = args.size();
|
||||||
|
if (argc < 1){
|
||||||
|
print_repositories(config);
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check which subcommand is supplied */
|
||||||
|
string sub_command = args.front();
|
||||||
|
args_t argv(args.begin()+1, args.end());
|
||||||
|
if(argv.size() < 2){
|
||||||
|
error error(
|
||||||
|
constants::error::INVALID_ARGS,
|
||||||
|
"Invalid number of args"
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
string name = argv[1];
|
||||||
|
string url = argv[2];
|
||||||
|
if(sub_command == "add") add_repositories(config, {{name, url}});
|
||||||
|
else if (sub_command == "remove") remove_respositories(config, argv);
|
||||||
|
// else if (sub_command == "set") set_repositories(config::context &context, const repository_map &repos)
|
||||||
|
else if (sub_command == "list") print_repositories(config);
|
||||||
|
else{
|
||||||
|
error error(
|
||||||
|
constants::error::UNKNOWN,
|
||||||
|
"Unknown sub-command. Try 'gdpm help remote' for options."
|
||||||
|
);
|
||||||
|
log::error(error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void set_repositories(
|
||||||
|
config::context& config,
|
||||||
|
const repository_map &repos
|
||||||
|
){
|
||||||
|
config.remote_sources = repos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void add_repositories(
|
||||||
|
config::context& config,
|
||||||
|
const repository_map &repos
|
||||||
|
){
|
||||||
|
std::for_each(repos.begin(), repos.end(),
|
||||||
|
[&config](const string_pair& p){
|
||||||
|
config.remote_sources.insert(p);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void remove_respositories(
|
||||||
|
config::context& config,
|
||||||
|
const repo_names& names
|
||||||
|
){
|
||||||
|
std::for_each(names.end(), names.begin(), [&config](const string& repo){
|
||||||
|
config.remote_sources.erase(repo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void move_respository(
|
||||||
|
config::context& config,
|
||||||
|
int old_position,
|
||||||
|
int new_position
|
||||||
|
){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_repositories(const config::context& config){
|
||||||
|
log::println("Remote sources:");
|
||||||
|
const auto &rs = config.remote_sources;
|
||||||
|
std::for_each(rs.begin(), rs.end(), [](const string_pair& p){
|
||||||
|
log::println("\t{}: {}", p.first, p.second);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/rest_api.cpp
111
src/rest_api.cpp
|
|
@ -15,16 +15,12 @@
|
||||||
#include <curlpp/Exception.hpp>
|
#include <curlpp/Exception.hpp>
|
||||||
|
|
||||||
namespace gdpm::rest_api{
|
namespace gdpm::rest_api{
|
||||||
bool register_account(const std::string& username, const std::string& password, const std::string& email){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool login(const std::string& username, const std::string& password){
|
context make_from_config(const config::context& config){
|
||||||
return false;
|
context params = make_context();
|
||||||
}
|
params.godot_version = config.godot_version;
|
||||||
|
params.verbose = config.verbose;
|
||||||
bool logout(){
|
return params;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context make_context(type_e type, int category, support_e support, const std::string& filter, const std::string& user, const std::string& godot_version, int max_results, int page, sort_e sort, bool reverse, int verbose){
|
context make_context(type_e type, int category, support_e support, const std::string& filter, const std::string& user, const std::string& godot_version, int max_results, int page, sort_e sort, bool reverse, int verbose){
|
||||||
|
|
@ -44,7 +40,29 @@ namespace gdpm::rest_api{
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document _parse_json(const std::string& r, int verbose){
|
bool register_account(
|
||||||
|
const string& username,
|
||||||
|
const string& password,
|
||||||
|
const string& email
|
||||||
|
){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool login(
|
||||||
|
const string& username,
|
||||||
|
const string& password
|
||||||
|
){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logout(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document _parse_json(
|
||||||
|
const string& r,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
Document d;
|
Document d;
|
||||||
d.Parse(r.c_str());
|
d.Parse(r.c_str());
|
||||||
|
|
@ -58,7 +76,7 @@ namespace gdpm::rest_api{
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string(type_e type){
|
string to_string(type_e type){
|
||||||
std::string _s{"type="};
|
std::string _s{"type="};
|
||||||
switch(type){
|
switch(type){
|
||||||
case any: _s += "any"; break;
|
case any: _s += "any"; break;
|
||||||
|
|
@ -68,8 +86,8 @@ namespace gdpm::rest_api{
|
||||||
return _s;
|
return _s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string(support_e support){
|
string to_string(support_e support){
|
||||||
std::string _s{"support="};
|
string _s{"support="};
|
||||||
switch(support){
|
switch(support){
|
||||||
case all: _s += "official+community+testing"; break;
|
case all: _s += "official+community+testing"; break;
|
||||||
case official: _s += "official"; break;
|
case official: _s += "official"; break;
|
||||||
|
|
@ -79,8 +97,8 @@ namespace gdpm::rest_api{
|
||||||
return _s;
|
return _s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string(sort_e sort){
|
string to_string(sort_e sort){
|
||||||
std::string _s{"sort="};
|
string _s{"sort="};
|
||||||
switch(sort){
|
switch(sort){
|
||||||
case none: _s += ""; break;
|
case none: _s += ""; break;
|
||||||
case rating: _s += "rating"; break;
|
case rating: _s += "rating"; break;
|
||||||
|
|
@ -91,16 +109,19 @@ namespace gdpm::rest_api{
|
||||||
return _s;
|
return _s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string _prepare_request(const std::string &url, const context &c){
|
string _prepare_request(
|
||||||
std::string request_url{url};
|
const string &url,
|
||||||
|
const context &c
|
||||||
|
){
|
||||||
|
string request_url{url};
|
||||||
request_url += to_string(c.type);
|
request_url += to_string(c.type);
|
||||||
request_url += (c.category <= 0) ? "&category=" : "&category="+fmt::to_string(c.category);
|
request_url += (c.category <= 0) ? "&category=" : "&category="+std::to_string(c.category);
|
||||||
request_url += "&" + to_string(c.support);
|
request_url += "&" + to_string(c.support);
|
||||||
request_url += "&" + to_string(c.sort);
|
request_url += "&" + to_string(c.sort);
|
||||||
request_url += (!c.filter.empty()) ? "&filter="+c.filter : "";
|
request_url += (!c.filter.empty()) ? "&filter="+c.filter : "";
|
||||||
request_url += (!c.godot_version.empty()) ? "&godot_version="+c.godot_version : "";
|
request_url += (!c.godot_version.empty()) ? "&godot_version="+c.godot_version : "";
|
||||||
request_url += "&max_results=" + fmt::to_string(c.max_results);
|
request_url += "&max_results=" + std::to_string(c.max_results);
|
||||||
request_url += "&page=" + fmt::to_string(c.page);
|
request_url += "&page=" + std::to_string(c.page);
|
||||||
request_url += (c.reverse) ? "&reverse" : "";
|
request_url += (c.reverse) ? "&reverse" : "";
|
||||||
return request_url;
|
return request_url;
|
||||||
}
|
}
|
||||||
|
|
@ -122,8 +143,12 @@ namespace gdpm::rest_api{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document configure(const std::string& url, type_e type, int verbose){
|
rapidjson::Document configure(
|
||||||
std::string request_url{url};
|
const string& url,
|
||||||
|
type_e type,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
|
string request_url{url};
|
||||||
request_url += to_string(type);
|
request_url += to_string(type);
|
||||||
http::response r = http::request_get(url);
|
http::response r = http::request_get(url);
|
||||||
if(verbose > 0)
|
if(verbose > 0)
|
||||||
|
|
@ -131,7 +156,20 @@ namespace gdpm::rest_api{
|
||||||
return _parse_json(r.body);
|
return _parse_json(r.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document get_assets_list(const std::string& url, type_e type, int category, support_e support, const std::string& filter,const std::string& user, const std::string& godot_version, int max_results, int page, sort_e sort, bool reverse, int verbose){
|
rapidjson::Document get_assets_list(
|
||||||
|
const string& url,
|
||||||
|
type_e type,
|
||||||
|
int category,
|
||||||
|
support_e support,
|
||||||
|
const string& filter,
|
||||||
|
const string& user,
|
||||||
|
const string& godot_version,
|
||||||
|
int max_results,
|
||||||
|
int page,
|
||||||
|
sort_e sort,
|
||||||
|
bool reverse,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
context c{
|
context c{
|
||||||
.type = type,
|
.type = type,
|
||||||
.category = category,
|
.category = category,
|
||||||
|
|
@ -148,16 +186,23 @@ namespace gdpm::rest_api{
|
||||||
return get_assets_list(url, c);
|
return get_assets_list(url, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document get_assets_list(const std::string& url, const context& c){
|
rapidjson::Document get_assets_list(
|
||||||
std::string request_url = _prepare_request(url, c);
|
const string& url,
|
||||||
|
const context& c
|
||||||
|
){
|
||||||
|
string request_url = _prepare_request(url, c);
|
||||||
http::response r = http::request_get(request_url);
|
http::response r = http::request_get(request_url);
|
||||||
if(c.verbose > 0)
|
if(c.verbose > 0)
|
||||||
log::info("URL: {}", request_url);
|
log::info("URL: {}", request_url);
|
||||||
return _parse_json(r.body, c.verbose);
|
return _parse_json(r.body, c.verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document get_asset(const std::string& url, int asset_id, const context& params){
|
rapidjson::Document get_asset(
|
||||||
std::string request_url = _prepare_request(url, params);
|
const string& url,
|
||||||
|
int asset_id,
|
||||||
|
const context& params
|
||||||
|
){
|
||||||
|
string request_url = _prepare_request(url, params);
|
||||||
utils::replace_all(request_url, "{id}", std::to_string(asset_id));
|
utils::replace_all(request_url, "{id}", std::to_string(asset_id));
|
||||||
http::response r = http::request_get(request_url.c_str());
|
http::response r = http::request_get(request_url.c_str());
|
||||||
if(params.verbose > 0)
|
if(params.verbose > 0)
|
||||||
|
|
@ -187,16 +232,16 @@ namespace gdpm::rest_api{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string review_asset_edit(int asset_id){
|
string review_asset_edit(int asset_id){
|
||||||
return std::string();
|
return string();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string accept_asset_edit(int asset_id){
|
string accept_asset_edit(int asset_id){
|
||||||
return std::string();
|
return string();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string reject_asset_edit(int asset_id){
|
string reject_asset_edit(int asset_id){
|
||||||
return std::string();
|
return string();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace edits
|
} // namespace edits
|
||||||
|
|
|
||||||
|
|
@ -66,14 +66,22 @@ namespace gdpm::utils{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string replace_first(std::string &s, const std::string &from, const std::string &to){
|
std::string replace_first(
|
||||||
|
std::string &s,
|
||||||
|
const std::string &from,
|
||||||
|
const std::string &to
|
||||||
|
){
|
||||||
size_t pos = s.find(from);
|
size_t pos = s.find(from);
|
||||||
if(pos == std::string::npos)
|
if(pos == std::string::npos)
|
||||||
return s;
|
return s;
|
||||||
return s.replace(pos, from.length(), to);
|
return s.replace(pos, from.length(), to);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string replace_all(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
|
||||||
|
){
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while((pos = s.find(from, pos)) != std::string::npos){
|
while((pos = s.find(from, pos)) != std::string::npos){
|
||||||
s.replace(pos, s.length(), to);
|
s.replace(pos, s.length(), to);
|
||||||
|
|
@ -83,7 +91,11 @@ namespace gdpm::utils{
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ref: https://gist.github.com/mobius/1759816 */
|
/* Ref: https://gist.github.com/mobius/1759816 */
|
||||||
int extract_zip(const char *archive, const char *dest, int verbose){
|
int extract_zip(
|
||||||
|
const char *archive,
|
||||||
|
const char *dest,
|
||||||
|
int verbose
|
||||||
|
){
|
||||||
const char *prog = "gpdm";
|
const char *prog = "gpdm";
|
||||||
struct zip *za;
|
struct zip *za;
|
||||||
struct zip_file *zf;
|
struct zip_file *zf;
|
||||||
|
|
@ -179,4 +191,15 @@ namespace gdpm::utils{
|
||||||
sleep_for(millis);
|
sleep_for(millis);
|
||||||
// sleep_until(system_clock::now() + millis);
|
// sleep_until(system_clock::now() + millis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string join(
|
||||||
|
const std::vector<std::string>& target,
|
||||||
|
const std::string& delimiter
|
||||||
|
){
|
||||||
|
std::string o;
|
||||||
|
std::for_each(target.begin(), target.end(), [&o, &delimiter](const std::string& s){
|
||||||
|
o += s + delimiter;
|
||||||
|
});
|
||||||
|
return o;
|
||||||
|
}
|
||||||
} // namespace gdpm::utils
|
} // namespace gdpm::utils
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "cache.hpp"
|
#include "cache.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "package.hpp"
|
||||||
|
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE("Cache functions"){
|
TEST_SUITE("Caching functions"){
|
||||||
|
|
||||||
TEST_CASE("Test cache database functions"){
|
TEST_CASE("Test cache database functions"){
|
||||||
gdpm::cache::create_package_database();
|
gdpm::cache::create_package_database();
|
||||||
|
|
@ -21,33 +22,34 @@ TEST_SUITE("Command functions"){
|
||||||
using namespace gdpm::package_manager;
|
using namespace gdpm::package_manager;
|
||||||
|
|
||||||
config::context config = config::make_context();
|
config::context config = config::make_context();
|
||||||
std::vector<std::string> packages{"ResolutionManagerPlugin","godot-hmac", "Godot"};
|
package::params params = package::params();
|
||||||
|
package::title_list package_titles{"ResolutionManagerPlugin","godot-hmac", "Godot"};
|
||||||
|
|
||||||
auto check_error = [](const error& error){
|
auto check_error = [](const error& error){
|
||||||
if(error.has_error()){
|
if(error.has_occurred()){
|
||||||
log::error(error.get_message());
|
log::error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK(!error.has_error());
|
CHECK(!error.has_occurred());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("Test install packages"){
|
TEST_CASE("Test install packages"){
|
||||||
check_error(install_packages(packages, true));
|
check_error(package::install(config, package_titles, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("Test searching packages"){
|
TEST_CASE("Test searching packages"){
|
||||||
check_error(search_for_packages(packages, true));
|
check_error(package::search(config, package_titles, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("Test remove packages"){
|
TEST_CASE("Test remove packages"){
|
||||||
check_error(remove_packages(packages, true));
|
check_error(package::remove(config, package_titles, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Test exporting installed package list"){
|
TEST_CASE("Test exporting installed package list"){
|
||||||
check_error(export_packages({"tests/gdpm/.tmp/packages.txt"}));
|
check_error(package::export_to({"tests/gdpm/.tmp/packages.txt"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue