mirror of
https://github.com/davidallendj/gdpm.git
synced 2025-12-20 03:27:02 -07:00
Small fixes
Updated CMakeLists.txt Updated README.md Updated bin/gdpm-test.sh Changed some constants defined in constants.hpp Added GDPM_TIMESTAMP_FORMAT configurable macro
This commit is contained in:
parent
1893c7c36b
commit
2b250d9a2d
11 changed files with 288 additions and 120 deletions
|
|
@ -21,21 +21,27 @@ find_package(fmt CONFIG REQUIRED)
|
|||
find_package(Catch2 CONFIG REQUIRED)
|
||||
find_package(cxxopts CONFIG REQUIRED)
|
||||
find_package(Poco CONFIG REQUIRED COMPONENTS Net JSON Util)
|
||||
find_package(libzip CONFIG REQUIRED)
|
||||
find_package(unofficial-sqlite3 CONFIG REQUIRED) # ...used with vcpkg unsuccessfully...
|
||||
|
||||
set(CMAKE_CXX_COMPILER "clang++")
|
||||
set(CMAKE_BUILD_RPATH "build/cmake")
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} -std=c++20 -Ofast -fPIC -fPIE -fpermissive -Wall -Wno-switch -Wno-unused-variable -Wno-sign-conversion -pedantic-errors"
|
||||
)
|
||||
set(INCLUDE_DIRS
|
||||
"include"
|
||||
${RAPIDJSON_INCLUDE_DIRS}
|
||||
${SQLite3_INCLUDE_DIRS}
|
||||
)
|
||||
set(LINK_LIBS
|
||||
fmt::fmt
|
||||
Threads::Threads
|
||||
Catch2::Catch2
|
||||
cxxopts::cxxopts
|
||||
unofficial::sqlite3::sqlite3
|
||||
-lcurlpp
|
||||
-lzip
|
||||
)
|
||||
|
||||
# Set library and executable targets
|
||||
|
|
@ -54,4 +60,4 @@ target_link_libraries(${PROJECT_NAME}-shared PRIVATE ${LINK_LIBS})
|
|||
target_link_libraries(${PROJECT_NAME}-static PRIVATE ${LINK_LIBS})
|
||||
|
||||
# Add project unit tests
|
||||
add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS})
|
||||
# add_custom_target("${PROJECT_NAME}-tests" SOURCE ${TESTS})
|
||||
|
|
|
|||
99
README.md
99
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Godot Package Manager (GDPM)
|
||||
|
||||
A front-end, package manager designed to handle assets from the Godot Game Engine's official asset library. It is written in C++ to be lightwight and fast with a few common dependencies found in most Linux distributions and can be used completely independent of Godot. It is designed to add more functionality not included with the AssetLib with the ability to automate builds on different systems. So far, the package manager is capable of searching, downloading, installing and removing packages and makes managing Godot assets across multiple projects much easier. GDPM has not been tested on Windows yet and there are no currently plans to support macOS.
|
||||
GDPM is an attempt to make a front-end, package manager designed to handle assets from the Godot Game Engine's official asset library. It is written in C++ to be lightwight and fast with a few common dependencies found in most Linux distributions and can be used completely independent of Godot. It is designed to add more functionality not included with the AssetLib with the ability to automate builds on different systems. So far, the package manager is capable of searching, downloading, installing and removing packages and makes managing Godot assets across multiple projects much easier. GDPM has not been tested on Windows yet and there are no currently plans to support macOS.
|
||||
|
||||
## Why not just use the Asset Library?
|
||||
|
||||
|
|
@ -8,7 +8,15 @@ The AssetLib is a bit limited for my use case. Personally, I wanted a way to qui
|
|||
|
||||
Some assets are more common such as the "First Person Controller" are likely to be reused with little to no modification. Therefore, it made sense to provide some method to download and install the assets once and then provide a symbolic link back to the stored packages. GDPM attempts to fix this by storing the assets in a single location and creating symlinks/shortcuts to each project. By have a separate, simple binary executable, the package manager can be used in shell scripts to automate tasks and download assets quickly.
|
||||
|
||||
Currently, there is no dependency management as it is not needed. There are future plans to implement a system to handle dependencies, but it is not a priority.
|
||||
Currently, there is no dependency management as it is not needed. There are future plans to implement a system to handle dependencies, but the implementation is still being fleshed out.
|
||||
|
||||
## How It Works
|
||||
|
||||
GDPM is built to work with an instance of [Godot's Asset API](https://github.com/godotengine/godot-asset-library). When installing packages, GDPM will first make HTTP requests to retrieve required asset data to download the asset and sync it in with a local database. Then, it will make another API request to retrieve the rest of an asset's data, update it in the database, then download it from the remote repository. Unfortunately, the package manager requires at least 2 request for a single asset. Therefore, there's an intentional 200 ms delay by default between each request to not bombard the API server, but is configurable at compile time (see "Building from source" section below).
|
||||
|
||||
When removing a package, the unzip files are remove but the temporary files are keep in the .tmp directory unless using the '--clean' option. This allows the user to remove the uncompressed files, but easily reinstall the package without having to download it remotely again.
|
||||
|
||||
The local database stores all the information sent by the Asset API with additional information like "install_path" and "remote_source" for the API used to gather information.
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -20,15 +28,11 @@ Currently, there is no dependency management as it is not needed. There are futu
|
|||
|
||||
- Create package groups to copy a set of packages into a Godot project.
|
||||
|
||||
- Login and register for access to repositories. (Note: Planned. This feature is not officially supported by Godot's official AssetLib.)
|
||||
-
|
||||
|
||||
- Handle dependencies between multiple assets. (Note: Planned. This feature is not supported by Godot's official AssetLib.)
|
||||
## Building from Source
|
||||
|
||||
- Support for paid assets. (Note: Planned. This feature is not supported by Godot's official AssetLib repository.)
|
||||
|
||||
## Building from source
|
||||
|
||||
The project uses the CMake or Meson build system and has been tested with GCC and Clang on Arch/Manjaro Linux. Meson is preferred, but a CMakeLists.txt is provided as well. Building on Windows has not been tested yet so it's not guaranteed to work. Compiling with CMake will build an executable, shared, and archive library. Compiling with Meson only builds an executable and shared library.
|
||||
The project uses the CMake or Meson build system and has been tested with GCC and Clang on Arch/Manjaro Linux. Meson is preferred, but a CMakeLists.txt is provided due to CMake's popularity and should work with some tweaking. Building on Windows has not been tested yet so it's not guaranteed to work. Compiling with CMake will build an executable, shared, and archive library, while compiling with Meson only builds an executable that links with a shared library.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
|
@ -42,39 +46,74 @@ The project uses the CMake or Meson build system and has been tested with GCC an
|
|||
|
||||
- RapidJson
|
||||
|
||||
- fmt (may be remove later in favor of C++20 std::format)
|
||||
|
||||
- Git (optional for cloning repository)
|
||||
- fmt (may be removed later in favor of C++20 std::format)
|
||||
|
||||
- Catch2 (optional for tests, but still WIP)
|
||||
|
||||
- cxxopts
|
||||
- cxxopts (header only)
|
||||
|
||||
- SQLite 3
|
||||
|
||||
After installing all necessary dependencies, open a terminal and paste the following commands.
|
||||
After installing all necessary dependencies, run the following commands:
|
||||
|
||||
```bash
|
||||
# Start by cloning the repo, then...
|
||||
git clone $(repo_name)
|
||||
cd $(repo_name)
|
||||
|
||||
# ...if using Meson on Linux
|
||||
# ...if using Meson with Clang on Linux...
|
||||
meson build
|
||||
meson configure build
|
||||
meson configure build # only needed if reconfiguring build
|
||||
meson compile -C build -j$(nproc)
|
||||
|
||||
# ... build using Clang++
|
||||
CXX=clang++ meson compile -C build -j$(npoc)
|
||||
|
||||
# ... if using CMake on Linux
|
||||
# ...if using CMake on Linux (needs work!)...
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
# ...build using MinGW on Windows ???
|
||||
# ...build using MSVC on Windows ???
|
||||
# ...build using Clang on MacOS ???
|
||||
```
|
||||
|
||||
Some macros can be set to enable/disable features or configure defaults by setting the C++ arguments in Meson. These values are used at compile time and a description of each value is provided below.
|
||||
|
||||
```bash
|
||||
cpp_args = [
|
||||
...
|
||||
'-DGDPM_LOG_LEVEL=2',
|
||||
'-DGDPM_REQUEST_DELAY=200ms',
|
||||
'-DGDPM_ENABLE_COLORS=1',
|
||||
'-DGDPM_ENABLE_TIMESTAMPS=1'
|
||||
...
|
||||
]
|
||||
|
||||
This can be done in CMake in a similar fashion:
|
||||
|
||||
```cmake
|
||||
if(MSVC)
|
||||
add_compile_defintions(
|
||||
# Add flags specific to MSVC here...
|
||||
)
|
||||
else()
|
||||
add_compile_definitions(
|
||||
-DGDPM_LOG_LEVEL=2
|
||||
-DGDPM_REQUEST_DELAY=200ms
|
||||
-DGDPM_ENABLE_COLOR=1
|
||||
-DGDPM_ENABLE_TIMESTAMP=1
|
||||
)
|
||||
endif()
|
||||
```
|
||||
|
||||
| Macro | Values | Description |
|
||||
| --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| GDPM_LOG_LEVEL | 0-5 | Sets the logging level to show when the program runs. Setting GDPM_LOG_LEVEL=0 will completely disable logging information being displayed. |
|
||||
| GDPM_REQUEST_DELAY | 200 ms | Sets the delay time (in ms) between each API request. The delay is meant to reduce the load on the API. |
|
||||
| GDPM_ENABLE_COLOR | 0 or 1 (true/false) | Enable/disable color output to stdout for errors and debugging. Disable if your system or console does not support ANSI colors. (default: true) |
|
||||
| GDPM_ENABLE_TIMESTAMP | 0 or 1 (true/false) | Enable/disable timestamp output to stdout. (default: true) |
|
||||
| GDPM_TIMESTAMP_FORMAT | strftime formated string (default: ":%I:%M:%S %p; %Y-%m-%d") | Set the time format to use in logging via strftime(). Uses ISO 8601 date and time standard as default. |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
GDPM takes a single command such as install, remove, search, or list followed by a list of package names as the following argument. The command specified is ran then the program exits reporting an errors that may occur. Each command can be modified by specifying additional optional parameters such as '--file' to specify a file to use or '--max-results' to set the number of max results to return from a search.
|
||||
|
|
@ -138,6 +177,7 @@ Planned: Add a custom remote AssetLib repository using [this](https://github.com
|
|||
|
||||
```bash
|
||||
gdpm add-remote https://custom-assetlib/asset-library/api --set-priority 0
|
||||
gdpm delete-remote https://custom-assetlib/asset-library/api
|
||||
```
|
||||
|
||||
Search for available packages from all added repositories. The results can be tweaked using a variety of options like '--sort' or '--support'. See the '--help' command for more details.
|
||||
|
|
@ -146,7 +186,7 @@ Search for available packages from all added repositories. The results can be tw
|
|||
gdpm search "GodotNetworking" \
|
||||
--sort updated \
|
||||
--type project \
|
||||
--max-results 100 \
|
||||
--max-results 50 \
|
||||
--godot-version 3.4 \
|
||||
--verbose \
|
||||
--user towk \
|
||||
|
|
@ -156,9 +196,26 @@ gdpm search "GodotNetworking" \
|
|||
To see more logging information, set the '--verbose' flag using an integer value between 0-5.
|
||||
|
||||
```bash
|
||||
gdpm list --verbose
|
||||
gdpm list packages --verbose
|
||||
gdpm list remote-sources
|
||||
```
|
||||
|
||||
## Planned Features
|
||||
|
||||
- [ ] Dependency management. There is no way of handling dependencies using the Godot Asset API. This is a [hot topic](https://github.com/godotengine/godot-proposals/issues/142) and might change in the near future.
|
||||
|
||||
- [ ] Proper updating mechanism.
|
||||
|
||||
- [ ] Plugin integration into the editor.
|
||||
|
||||
- [ ] Complete integration with the Asset API including moderation tools.
|
||||
|
||||
- [ ] Login and register for access to repositories.
|
||||
|
||||
- [ ] Handle dependencies between multiple assets.
|
||||
|
||||
- [ ] Support for paid assets.
|
||||
|
||||
## License
|
||||
|
||||
See the LICENSE.md file.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
command=build/gdpm
|
||||
|
||||
# Install packages using install command and specifying each package name or file
|
||||
${command} install "ResolutionManagerPlugin" "godot-hmac"
|
||||
${command} install -f packages.txt --clean --godot-version 3.4
|
||||
|
|
@ -12,10 +13,12 @@ ${command} sync
|
|||
|
||||
# Search for packages containing "Godot" in package name
|
||||
${command} search "Godot" --config config.json
|
||||
${command} search "Godot" --config config.json --no-sync
|
||||
${command} search "Godot" --no-sync --godot-version 3.4 --max-results 20 --sort updated
|
||||
|
||||
# List all currently installed packages
|
||||
${command} list
|
||||
${command} list packages
|
||||
${command} list remote-sources
|
||||
|
||||
# Create a symlink of packages to specified path
|
||||
${command} link "ResolutionManagerPlugin" "godot-hmac" --path tests/tests-godot-project
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ namespace gdpm::constants{
|
|||
#define GDPM_DEFAULT_ASSET_SUPPORT all
|
||||
#define GDPM_DEFAULT_ASSET_FILTER ""
|
||||
#define GDPM_DEFAULT_ASSET_USER ""
|
||||
#define GDPM_DEFAULT_ASSET_GODOT_VERSION ""
|
||||
#define GDPM_DEFAULT_ASSET_GODOT_VERSION "3.4"
|
||||
#define GDPM_DEFAULT_ASSET_MAX_RESULTS 500
|
||||
#define GDPM_DEFAULT_ASSET_PAGE 0
|
||||
#define GDPM_DEFAULT_ASSET_SORT none
|
||||
|
|
@ -72,3 +72,7 @@ namespace gdpm::constants{
|
|||
#ifndef GDPM_ENABLE_TIMESTAMPS
|
||||
#define GDPM_ENABLE_TIMESTAMPS 1
|
||||
#endif
|
||||
|
||||
#ifndef GDPM_TIMESTAMP_FORMAT
|
||||
#define GDPM_TIMESTAMP_FORMAT ":%I:%M:%S %p; %Y-%m-%d"
|
||||
#endif
|
||||
|
|
@ -34,6 +34,7 @@ namespace gdpm::package_manager{
|
|||
std::string download_hash;
|
||||
bool is_installed;
|
||||
std::string install_path;
|
||||
std::vector<package_info> dependencies;
|
||||
};
|
||||
|
||||
struct cxxargs{
|
||||
|
|
@ -64,8 +65,7 @@ namespace gdpm::package_manager{
|
|||
GDPM_DLL_EXPORT void remove_packages(const std::vector<std::string>& package_titles);
|
||||
GDPM_DLL_EXPORT void update_packages(const std::vector<std::string>& package_titles);
|
||||
GDPM_DLL_EXPORT void search_for_packages(const std::vector<std::string>& package_titles);
|
||||
GDPM_DLL_EXPORT void list_installed_packages();
|
||||
GDPM_DLL_EXPORT void read_package_contents(const std::string& package_title);
|
||||
GDPM_DLL_EXPORT void list_information(const std::vector<std::string>& opts);
|
||||
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);
|
||||
|
|
@ -78,6 +78,8 @@ namespace gdpm::package_manager{
|
|||
GDPM_DLL_EXPORT void run_command(command_e command, const std::vector<std::string>& package_titles, const std::vector<std::string>& opts);
|
||||
GDPM_DLL_EXPORT void print_package_list(const rapidjson::Document& json);
|
||||
GDPM_DLL_EXPORT void print_package_list(const std::vector<package_info>& packages);
|
||||
GDPM_DLL_EXPORT void print_remote_sources();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ namespace gdpm::rest_api{
|
|||
enum support_e { all, official, community, testing };
|
||||
enum sort_e { none, rating, cost, name, updated };
|
||||
|
||||
struct asset_list_context{
|
||||
struct rest_api_context{
|
||||
type_e type;
|
||||
int category;
|
||||
support_e support;
|
||||
|
|
@ -47,13 +47,14 @@ namespace gdpm::rest_api{
|
|||
int verbose;
|
||||
};
|
||||
|
||||
asset_list_context make_context(type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const std::string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE);
|
||||
rest_api_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(support_e support);
|
||||
std::string to_string(sort_e sort);
|
||||
void _print_params(const rest_api_context& params);
|
||||
rapidjson::Document _parse_json(const std::string& r, int verbose = 0);
|
||||
std::string _get_type_string(type_e type);
|
||||
std::string _get_support_string(support_e support);
|
||||
std::string _get_sort_string(sort_e sort);
|
||||
void _print_params(const asset_list_context& params);
|
||||
std::string _prepare_request(const std::string& url, const rest_api_context& context);
|
||||
|
||||
bool register_account(const std::string& username, const std::string& password, const std::string& email);
|
||||
bool login(const std::string& username, const std::string& password);
|
||||
|
|
@ -61,8 +62,8 @@ namespace gdpm::rest_api{
|
|||
|
||||
rapidjson::Document configure(const std::string& url = constants::HostUrl, type_e type = any, int verbose = 0);
|
||||
rapidjson::Document get_assets_list(const std::string& url = constants::HostUrl, type_e type = GDPM_DEFAULT_ASSET_TYPE, int category = GDPM_DEFAULT_ASSET_CATEGORY, support_e support = GDPM_DEFAULT_ASSET_SUPPORT, const std::string& filter = GDPM_DEFAULT_ASSET_FILTER, const std::string& user = GDPM_DEFAULT_ASSET_USER, const std::string& godot_version = GDPM_DEFAULT_ASSET_GODOT_VERSION, int max_results = GDPM_DEFAULT_ASSET_MAX_RESULTS, int page = GDPM_DEFAULT_ASSET_PAGE, sort_e sort = GDPM_DEFAULT_ASSET_SORT, bool reverse = GDPM_DEFAULT_ASSET_REVERSE, int verbose = GDPM_DEFAULT_ASSET_VERBOSE);
|
||||
rapidjson::Document get_assets_list(const std::string& url, const asset_list_context& params);
|
||||
rapidjson::Document get_asset(const std::string& url, int asset_id, int verbose = GDPM_DEFAULT_ASSET_VERBOSE);
|
||||
rapidjson::Document get_assets_list(const std::string& url, const rest_api_context& params = {});
|
||||
rapidjson::Document get_asset(const std::string& url, int asset_id, const rest_api_context& params = {});
|
||||
bool delete_asset(int asset_id); // ...for moderators
|
||||
bool undelete_asset(int asset_id); // ...for moderators
|
||||
bool set_support_level(int asset_id); // ...for moderators
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace gdpm::utils{
|
|||
return fwrite(ptr, size, nmemb, (FILE*)userdata);
|
||||
}
|
||||
|
||||
static inline auto timestamp(const std::string& format = ":%I:%M:%S %p; %Y-%m-%d"){
|
||||
static inline auto timestamp(const std::string& format = GDPM_TIMESTAMP_FORMAT){
|
||||
time_t t = std::time(nullptr);
|
||||
#if GDPM_ENABLE_TIMESTAMPS == 1
|
||||
return fmt::format(fmt::runtime("{"+format+"}"), fmt::localtime(t));
|
||||
|
|
|
|||
|
|
@ -24,14 +24,15 @@ src = [
|
|||
'src/http.cpp',
|
||||
'src/cache.cpp'
|
||||
]
|
||||
# cpp = meson.get_compiler('cpp')
|
||||
# cpp.find_library()
|
||||
|
||||
cpp_args = [
|
||||
'-Ofast',
|
||||
# '-fPIC',
|
||||
# '-fPIE',
|
||||
# '-fconcepts',
|
||||
# '-fsanitize=address',
|
||||
# '-lcurl',
|
||||
# '-lzip'
|
||||
'-fpermissive',
|
||||
'-Wall',
|
||||
'-Wno-switch',
|
||||
|
|
@ -39,12 +40,11 @@ cpp_args = [
|
|||
'-Wno-sign-conversion',
|
||||
'-Wno-unused-function',
|
||||
'-pedantic-errors',
|
||||
# '-lcurl',
|
||||
# '-lzip'
|
||||
'-DGDPM_LOG_LEVEL=2',
|
||||
'-DGDPM_REQUEST_DELAY=200ms',
|
||||
'-DGDPM_ENABLE_COLORS=1',
|
||||
'-DGDPM_ENABLE_TIMESTAMPS=1',
|
||||
'-DGDPM_TIMESTAMP_FORMAT=":%I:%M:%S %p; %Y-%m-%d"'
|
||||
]
|
||||
lib = shared_library(
|
||||
meson.project_name(),
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@ namespace gdpm::cache{
|
|||
" download_hash='" + p.download_hash + "', " +
|
||||
" is_installed=" + fmt::to_string(p.is_installed) + ", "
|
||||
" install_path='" + p.install_path + "'"
|
||||
// " dependencies='" + p.dependencies + "'"
|
||||
" WHERE title='" + p.title + "' AND asset_id=" + fmt::to_string(p.asset_id)
|
||||
+ ";\n";
|
||||
}
|
||||
|
|
@ -387,6 +388,7 @@ namespace gdpm::cache{
|
|||
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) + ", ";
|
||||
p_values += "'" + p.type + "', ";
|
||||
p_values += "'" + utils::replace_all(p_title, "'", "''") + "', ";
|
||||
|
|
@ -413,4 +415,5 @@ namespace gdpm::cache{
|
|||
o += to_values(p);
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ namespace gdpm::package_manager{
|
|||
CURL *curl;
|
||||
CURLcode res;
|
||||
config::config_context config;
|
||||
rest_api::asset_list_context params;
|
||||
rest_api::rest_api_context params;
|
||||
command_e command;
|
||||
std::vector<std::string> packages;
|
||||
std::vector<std::string> opts;
|
||||
|
|
@ -84,6 +84,7 @@ namespace gdpm::package_manager{
|
|||
|
||||
void install_packages(const std::vector<std::string>& package_titles){
|
||||
using namespace rapidjson;
|
||||
params.verbose = config.verbose;
|
||||
|
||||
/* 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
|
||||
|
|
@ -95,7 +96,6 @@ namespace gdpm::package_manager{
|
|||
cache if possible. */
|
||||
if(config.enable_sync){
|
||||
if(p_cache.empty()){
|
||||
log::info("Synchronizing database...");
|
||||
p_cache = synchronize_database(package_titles);
|
||||
p_cache = cache::get_package_info_by_title(package_titles);
|
||||
}
|
||||
|
|
@ -135,9 +135,10 @@ namespace gdpm::package_manager{
|
|||
/* Retrieve necessary asset data if it was found already in cache */
|
||||
Document doc;
|
||||
if(p.download_url.empty() || p.category.empty() || p.description.empty() || p.support_level.empty()){
|
||||
doc = rest_api::get_asset(url, p.asset_id, config.verbose);
|
||||
doc = rest_api::get_asset(url, p.asset_id, params);
|
||||
if(doc.HasParseError() || doc.IsNull()){
|
||||
log::error("Could not get a response from server. ({})", doc.GetParseError());
|
||||
log::println("");
|
||||
log::error("Error parsing HTTP response. (error code: {})", doc.GetParseError());
|
||||
return;
|
||||
}
|
||||
p.category = doc["category"].GetString();
|
||||
|
|
@ -286,36 +287,50 @@ namespace gdpm::package_manager{
|
|||
using namespace rapidjson;
|
||||
/* If no package titles provided, update everything and then exit */
|
||||
if(package_titles.empty()){
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fetch remote asset data and compare to see if there are package updates */
|
||||
std::vector<package_info> p_updates = {};
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
|
||||
std::string url{constants::HostUrl};
|
||||
url += rest_api::endpoints::GET_Asset;
|
||||
url += rest_api::endpoints::GET_AssetId;
|
||||
Document doc = rest_api::get_assets_list(url, params);
|
||||
|
||||
if(doc.IsNull()){
|
||||
log::error("Could not get response from server. Aborting.");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto& p : p_updates){
|
||||
for(const auto& o : doc["result"].GetArray()){
|
||||
size_t local_version = std::stoul(p.version);
|
||||
std::string remote_version_s = o[""].GetString();
|
||||
/* Fetch remote asset data and compare to see if there are package updates */
|
||||
std::vector<std::string> p_updates = {};
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
|
||||
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(!skip_prompt){
|
||||
if(!utils::prompt_user_yn("Do you want to update the following packages? (y/n)"))
|
||||
return;
|
||||
}
|
||||
|
||||
remove_packages(p_updates);
|
||||
install_packages(p_updates);
|
||||
}
|
||||
|
||||
|
||||
void search_for_packages(const std::vector<std::string> &package_titles){
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
|
||||
if(!p_cache.empty()){
|
||||
if(!p_cache.empty() && !config.enable_sync){
|
||||
print_package_list(p_cache);
|
||||
return;
|
||||
}
|
||||
|
|
@ -341,9 +356,11 @@ namespace gdpm::package_manager{
|
|||
}
|
||||
|
||||
|
||||
void list_installed_packages(){
|
||||
void list_information(const std::vector<std::string>& opts){
|
||||
using namespace rapidjson;
|
||||
using namespace std::filesystem;
|
||||
|
||||
if(opts.empty() || opts[0] == "packages"){
|
||||
const path path{config.packages_dir};
|
||||
std::vector<package_info> p_installed = cache::get_installed_packages();
|
||||
if(p_installed.empty())
|
||||
|
|
@ -351,10 +368,13 @@ namespace gdpm::package_manager{
|
|||
log::println("Installed packages:");
|
||||
print_package_list(p_installed);
|
||||
}
|
||||
|
||||
|
||||
void read_package_contents(const std::string& package_title){
|
||||
|
||||
else if(opts[0] == "remote-sources"){
|
||||
print_remote_sources();
|
||||
}
|
||||
else{
|
||||
log::error("Unrecognized subcommand. Use either 'packages' or 'remote-sources' instead.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -378,6 +398,11 @@ namespace gdpm::package_manager{
|
|||
void link_packages(const std::vector<std::string>& package_titles, const std::vector<std::string>& paths){
|
||||
using namespace std::filesystem;
|
||||
|
||||
if(paths.empty()){
|
||||
log::error("No path set. Use '--path' option to set a path.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<package_info> p_found = {};
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
if(p_cache.empty()){
|
||||
|
|
@ -392,11 +417,16 @@ namespace gdpm::package_manager{
|
|||
}
|
||||
}
|
||||
|
||||
if(p_found.empty()){
|
||||
log::error("No packages found to link.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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("Creating symlink for \"{}\" package to {}", p.title, path + "/" + p.title);
|
||||
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};
|
||||
|
|
@ -407,6 +437,7 @@ namespace gdpm::package_manager{
|
|||
if(ec){
|
||||
log::error("Could not create symlink: {}", ec.message());
|
||||
}
|
||||
log::println("Done.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -415,6 +446,11 @@ namespace gdpm::package_manager{
|
|||
void clone_packages(const std::vector<std::string>& package_titles, const std::vector<std::string>& paths){
|
||||
using namespace std::filesystem;
|
||||
|
||||
if(paths.empty()){
|
||||
log::error("No path set. Use '--path' option to set a path.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<package_info> p_found = {};
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
if(p_cache.empty()){
|
||||
|
|
@ -429,6 +465,11 @@ namespace gdpm::package_manager{
|
|||
}
|
||||
}
|
||||
|
||||
if(p_found.empty()){
|
||||
log::error("No packages to clone.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the storage paths for all packages to create clones */
|
||||
const path package_dir{config.packages_dir};
|
||||
for(const auto& p : p_found){
|
||||
|
|
@ -463,12 +504,33 @@ namespace gdpm::package_manager{
|
|||
}
|
||||
|
||||
|
||||
/* TODO: Need to finish implementation...will do that when it's needed. */
|
||||
void delete_remote_repository(size_t index){
|
||||
auto& s = config.remote_sources;
|
||||
// std::erase(s, index);
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> resolve_dependencies(const std::vector<std::string>& package_titles){
|
||||
std::vector<std::string> p_deps = {};
|
||||
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
|
||||
|
||||
/* 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){
|
||||
auto temp = resolve_dependencies({d.title});
|
||||
utils::move_if_not(temp, p_deps, [](const std::string& p){ return true; });
|
||||
}
|
||||
}
|
||||
|
||||
return p_deps;
|
||||
}
|
||||
|
||||
|
||||
cxxargs parse_arguments(int argc, char **argv){
|
||||
/* Parse command-line arguments using cxxopts */
|
||||
cxxopts::Options options(
|
||||
|
|
@ -490,7 +552,9 @@ namespace gdpm::package_manager{
|
|||
("sync", "Sync local database with remote server.")
|
||||
("add-remote", "Set a source repository.", cxxopts::value<std::string>()->default_value(constants::AssetRepo), "<url>")
|
||||
("delete-remote", "Remove a source repository from list.", cxxopts::value<std::string>(), "<url>")
|
||||
("remote", "One time remote source use. Source is not saved and override sources used in config.", cxxopts::value<std::vector<std::string>>(), "<url>")
|
||||
("h,help", "Print this message and exit.")
|
||||
("version", "Show the version and exit.")
|
||||
;
|
||||
options.parse_positional({"input"});
|
||||
options.add_options("Options")
|
||||
|
|
@ -517,9 +581,6 @@ namespace gdpm::package_manager{
|
|||
|
||||
|
||||
void handle_arguments(const cxxargs& args){
|
||||
auto _get_package_list = [](const cxxopts::ParseResult& result, const char *arg){
|
||||
return result[arg].as<std::vector<std::string>>();
|
||||
};
|
||||
const auto& result = args.result;
|
||||
const auto& options = args.options;
|
||||
|
||||
|
|
@ -538,6 +599,9 @@ namespace gdpm::package_manager{
|
|||
auto iter = std::find(config.remote_sources.begin(), config.remote_sources.end(), repo);
|
||||
if(iter != config.remote_sources.end())
|
||||
config.remote_sources.erase(iter);
|
||||
}
|
||||
if(result.count("remote")){
|
||||
|
||||
}
|
||||
if(result.count("file")){
|
||||
std::string path = result["file"].as<std::string>();
|
||||
|
|
@ -548,8 +612,8 @@ namespace gdpm::package_manager{
|
|||
opts = result["path"].as<std::vector<std::string>>();
|
||||
}
|
||||
if(result.count("sort")){
|
||||
rest_api::sort_e sort = rest_api::sort_e::none;
|
||||
std::string r = result["sort"].as<std::string>();
|
||||
rest_api::sort_e sort = rest_api::sort_e::none;
|
||||
if(r == "none") sort = rest_api::sort_e::none;
|
||||
else if(r == "rating") sort = rest_api::sort_e::rating;
|
||||
else if(r == "cost") sort = rest_api::sort_e::cost;
|
||||
|
|
@ -558,16 +622,16 @@ namespace gdpm::package_manager{
|
|||
params.sort = sort;
|
||||
}
|
||||
if(result.count("type")){
|
||||
rest_api::type_e type = rest_api::type_e::any;
|
||||
std::string r = result["type"].as<std::string>();
|
||||
rest_api::type_e type = rest_api::type_e::any;
|
||||
if(r == "any") type = rest_api::type_e::any;
|
||||
else if(r == "addon") type = rest_api::type_e::addon;
|
||||
else if(r == "project") type = rest_api::type_e::project;
|
||||
params.type = type;
|
||||
}
|
||||
if(result.count("support")){
|
||||
rest_api::support_e support = rest_api::support_e::all;
|
||||
std::string r = result["support"].as<std::string>();
|
||||
rest_api::support_e support = rest_api::support_e::all;
|
||||
if(r == "all") support = rest_api::support_e::all;
|
||||
else if(r == "official") support = rest_api::support_e::official;
|
||||
else if(r == "community") support = rest_api::support_e::community;
|
||||
|
|
@ -642,6 +706,9 @@ namespace gdpm::package_manager{
|
|||
else if(argv[0] == "help" || argv[0] == "-h" || argv[0] == "--help"){
|
||||
log::println("{}", options.help());
|
||||
}
|
||||
else{
|
||||
log::error("Unrecognized command. Try 'gdpm help' for more info.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -652,7 +719,7 @@ namespace gdpm::package_manager{
|
|||
case remove: remove_packages(package_titles); break;
|
||||
case update: update_packages(package_titles); break;
|
||||
case search: search_for_packages(package_titles); break;
|
||||
case list: list_installed_packages(); break;
|
||||
case list: list_information(package_titles); break;
|
||||
/* ...opts are the paths here */
|
||||
case link: link_packages(package_titles, opts); break;
|
||||
case clone: clone_packages(package_titles, opts); break;
|
||||
|
|
@ -668,12 +735,12 @@ namespace gdpm::package_manager{
|
|||
|
||||
void print_package_list(const std::vector<package_info>& packages){
|
||||
for(const auto& p : packages){
|
||||
log::println("{}/{} {} id={}\n\t{}, Godot {}, {}, {}, Last Modified: {}",
|
||||
log::println("{}/{}/{} {} id={}\n\tGodot {}, {}, {}, Last Modified: {}",
|
||||
p.support_level,
|
||||
p.author,
|
||||
p.title,
|
||||
p.version,
|
||||
p.asset_id,
|
||||
p.author,
|
||||
p.godot_version,
|
||||
p.cost,
|
||||
p.category,
|
||||
|
|
@ -685,12 +752,12 @@ namespace gdpm::package_manager{
|
|||
|
||||
void print_package_list(const rapidjson::Document& json){
|
||||
for(const auto& o : json["result"].GetArray()){
|
||||
log::println("{}/{} {} id={}\n\t{}, Godot {}, {}, {}, Last Modified: {}",
|
||||
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["author"] .GetString(),
|
||||
o["godot_version"] .GetString(),
|
||||
o["cost"] .GetString(),
|
||||
o["category"] .GetString(),
|
||||
|
|
@ -700,6 +767,14 @@ namespace gdpm::package_manager{
|
|||
}
|
||||
|
||||
|
||||
void print_remote_sources(){
|
||||
log::println("Remote sources:");
|
||||
for(const auto& s : config.remote_sources){
|
||||
log::println("\t{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<package_info> synchronize_database(const std::vector<std::string>& package_titles){
|
||||
using namespace rapidjson;
|
||||
params.verbose = config.verbose;
|
||||
|
|
@ -711,6 +786,7 @@ namespace gdpm::package_manager{
|
|||
int total_items = 0;
|
||||
int items_left = 0;
|
||||
|
||||
log::info_n("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. */
|
||||
|
|
@ -720,7 +796,7 @@ namespace gdpm::package_manager{
|
|||
params.page += 1;
|
||||
|
||||
if(doc.IsNull()){
|
||||
log::error("Could not get response from server. Aborting.");
|
||||
log::error("\nCould not get response from server. Aborting.");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -765,6 +841,8 @@ namespace gdpm::package_manager{
|
|||
|
||||
} while(items_left > 0);
|
||||
|
||||
log::println("Done.");
|
||||
|
||||
return cache::get_package_info_by_title(package_titles);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ namespace gdpm::rest_api{
|
|||
return false;
|
||||
}
|
||||
|
||||
asset_list_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){
|
||||
asset_list_context params{
|
||||
rest_api_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){
|
||||
rest_api_context params{
|
||||
.type = type,
|
||||
.category = category,
|
||||
.support = support,
|
||||
|
|
@ -58,7 +58,7 @@ namespace gdpm::rest_api{
|
|||
return d;
|
||||
}
|
||||
|
||||
std::string _get_type_string(type_e type){
|
||||
std::string to_string(type_e type){
|
||||
std::string _s{"type="};
|
||||
switch(type){
|
||||
case any: _s += "any"; break;
|
||||
|
|
@ -68,7 +68,7 @@ namespace gdpm::rest_api{
|
|||
return _s;
|
||||
}
|
||||
|
||||
std::string _get_support_string(support_e support){
|
||||
std::string to_string(support_e support){
|
||||
std::string _s{"support="};
|
||||
switch(support){
|
||||
case all: _s += "official+community+testing"; break;
|
||||
|
|
@ -79,7 +79,7 @@ namespace gdpm::rest_api{
|
|||
return _s;
|
||||
}
|
||||
|
||||
std::string _get_sort_string(sort_e sort){
|
||||
std::string to_string(sort_e sort){
|
||||
std::string _s{"sort="};
|
||||
switch(sort){
|
||||
case none: _s += ""; break;
|
||||
|
|
@ -91,7 +91,21 @@ namespace gdpm::rest_api{
|
|||
return _s;
|
||||
}
|
||||
|
||||
void _print_params(const asset_list_context& params){
|
||||
std::string _prepare_request(const std::string &url, const rest_api_context &c){
|
||||
std::string request_url{url};
|
||||
request_url += to_string(c.type);
|
||||
request_url += (c.category <= 0) ? "&category=" : "&category="+fmt::to_string(c.category);
|
||||
request_url += "&" + to_string(c.support);
|
||||
request_url += "&" + to_string(c.sort);
|
||||
request_url += (!c.filter.empty()) ? "&filter="+c.filter : "";
|
||||
request_url += (!c.godot_version.empty()) ? "&godot_version="+c.godot_version : "";
|
||||
request_url += "&max_results=" + fmt::to_string(c.max_results);
|
||||
request_url += "&page=" + fmt::to_string(c.page);
|
||||
request_url += (c.reverse) ? "&reverse" : "";
|
||||
return request_url;
|
||||
}
|
||||
|
||||
void _print_params(const rest_api_context& params){
|
||||
log::println("params: \n"
|
||||
"\ttype: {}\n"
|
||||
"\tcategory: {}\n"
|
||||
|
|
@ -108,10 +122,9 @@ namespace gdpm::rest_api{
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Document configure(const std::string& url, type_e type, int verbose){
|
||||
std::string request_url{url};
|
||||
request_url += _get_type_string(type);
|
||||
request_url += to_string(type);
|
||||
http::response r = http::request_get(url);
|
||||
if(verbose > 0)
|
||||
log::info("URL: {}", url);
|
||||
|
|
@ -119,34 +132,35 @@ namespace gdpm::rest_api{
|
|||
}
|
||||
|
||||
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){
|
||||
std::string request_url{url};
|
||||
request_url += _get_type_string(type);
|
||||
request_url += (category <= 0) ? "&category=" : "&category="+fmt::to_string(category);
|
||||
request_url += "&" + _get_support_string(support);
|
||||
request_url += "&" + _get_sort_string(sort);
|
||||
request_url += (!filter.empty()) ? "&filter="+filter : "";
|
||||
request_url += (!godot_version.empty()) ? "&godot_version="+godot_version : "";
|
||||
request_url += "&max_results=" + fmt::to_string(max_results);
|
||||
request_url += "&page=" + fmt::to_string(page);
|
||||
request_url += (reverse) ? "&reverse" : "";
|
||||
rest_api_context c{
|
||||
.type = type,
|
||||
.category = category,
|
||||
.support = support,
|
||||
.filter = filter,
|
||||
.user = user,
|
||||
.godot_version = godot_version,
|
||||
.max_results = max_results,
|
||||
.page = page,
|
||||
.sort = sort,
|
||||
.reverse = reverse,
|
||||
.verbose = verbose
|
||||
};
|
||||
return get_assets_list(url, c);
|
||||
}
|
||||
|
||||
rapidjson::Document get_assets_list(const std::string& url, const rest_api_context& c){
|
||||
std::string request_url = _prepare_request(url, c);
|
||||
http::response r = http::request_get(request_url);
|
||||
if(verbose > 0)
|
||||
if(c.verbose > 0)
|
||||
log::info("URL: {}", request_url);
|
||||
return _parse_json(r.body, verbose);
|
||||
return _parse_json(r.body, c.verbose);
|
||||
}
|
||||
|
||||
rapidjson::Document get_assets_list(const std::string& url, const asset_list_context& params){
|
||||
return get_assets_list(
|
||||
url, params.type, params.category, params.support, params.filter, params.user, params.godot_version, params.max_results, params.page, params.sort, params.reverse, params.verbose
|
||||
);
|
||||
}
|
||||
|
||||
rapidjson::Document get_asset(const std::string& url, int asset_id, int verbose){
|
||||
std::string request_url{url};
|
||||
request_url = utils::replace_all(request_url, "{id}", fmt::to_string(asset_id));
|
||||
rapidjson::Document get_asset(const std::string& url, int asset_id, const rest_api_context& params){
|
||||
std::string request_url = _prepare_request(url, params);
|
||||
utils::replace_all(request_url, "{id}", std::to_string(asset_id));
|
||||
http::response r = http::request_get(request_url.c_str());
|
||||
if(verbose > 0)
|
||||
if(params.verbose > 0)
|
||||
log::info("URL: {}", request_url);
|
||||
return _parse_json(r.body);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue