Update API, README, added functions, and cleaned up implementations

- Changed the remote API
- Added `export` and changed `remote` commands
- Update README.md file
- Removed dependence on `zig c++` in `bin/build.sh` script
- Added error codes to use in place of ints
- Other minor changes
This commit is contained in:
David Allen 2023-05-12 22:27:45 -06:00
parent 88fd230ba6
commit 8e82a22ebd
8 changed files with 273 additions and 192 deletions

View file

@ -34,10 +34,7 @@ jobs:
echo -e "Building executable and libraries...\n$PWD"
./bin/compile.sh
test:
runs-on: [self-hosted, linux]
steps:
- name: Tests
- name: Test
run:
echo "Running unit tests..."
./bin/gdpm-tests

1
.trunk/actions Symbolic link
View file

@ -0,0 +1 @@
/home/david/.cache/trunk/repos/4c139ff7a07a0441c956056eef1736ac/actions

1
.trunk/notifications Symbolic link
View file

@ -0,0 +1 @@
/home/david/.cache/trunk/repos/4c139ff7a07a0441c956056eef1736ac/notifications

View file

@ -1,10 +1,12 @@
# Godot Package Manager (GDPM)
GDPM is an attempt to make a front-end, command-line, 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 official AssetLib with the ability to automate downloads for 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 to work on Windows or Mac.
GDPM is an attempt to make a simple, front-end, command-line, 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 official AssetLib with the ability to automate downloads for different platforms. So far, the package manager is capable of searching, downloading, installing, and removing packages and makes managing Godot assets across multiple projects much easier. It stores "packages" in a global directories and is capable of linking or copying global packages to local projects. 
\*GDPM has not been tested for Windows or Mac.
## Why not just use the Asset Library?
The AssetLib actually works well enough for most use cases. However, it's common to download the same asset multiple times for different projects. If there isn't a need to modify the asset itself, it makes more sense to download the asset only once. That way, there will only be one copy of the asset and it can be linked to all new projects. The AssetLib does not have a way to manage assets like this, and therefore requires redownload assets for each new project. Additionally, the AssetLib is integrated into the engine itself with no command-line interface. When downloading multiple assets, it is much more convenient to be able to automate the downloads using a list from a text file. If the user knows, which assets to download, they can make a list and point to it.
The AssetLib actually works well enough for most use cases. However, it's fairly common to download the same asset multiple times for different projects. If there isn't a need to modify the asset itself, it makes more sense to download the asset *once and only once*. That way, there will only be one global, read-only copy of the asset that is accessable and it can be linked to more than one project. The AssetLib does not have a built-in way to manage assets like this, and therefore requires redownloading assets for each new project. Additionally, the AssetLib is integrated into the engine itself with no command-line interface to manage assets. When downloading multiple assets, it is much more convenient to be able to automate the downloads using a list from a text file. If the user knows, which assets to download, they can make a list and point to it.
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 having a separate, simple binary executable, the package manager can be used in shell scripts to download assets more quickly.
@ -16,17 +18,17 @@ GDPM is built to work with an instance of [Godot's Asset API](https://github.com
When removing a package, the unzip files are remove but the temporary files are keep in the specified `.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.
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
- Download and install packages from Godot's official AssetLib or from custom repositories. (Note: Multithread download support planned.)
* Download and install packages from Godot's official AssetLib or from custom repositories. (Note: Multithread download support planned.)
- Stores downloads in single location to use across multiple Godot projects.
* Stores downloads in single location to use across multiple Godot projects.
- Copy packages into a Godot project to make modifications when needed.
* Copy packages into a Godot project to make modifications when needed.
- Create package groups to copy a set of packages into a Godot project.
* Create package groups to copy a set of packages into a Godot project.
## Building from Source
@ -34,30 +36,31 @@ The project uses the CMake or Meson build system and has been tested with GCC an
### Prerequisites
- Meson or CMake (version 3.12 or later)
* Meson or CMake (version 3.12 or later)
- C++20 compiler (GCC/Clang/MSVC??)
* C++20 compiler (GCC/Clang/MSVC??)
- libcurl (or curlpp)
* libcurl (or curlpp)
- libzip
* libzip
- RapidJson
* RapidJson
- fmt (may be removed later in favor of C++20 std::format)
* fmt (may be removed later in favor of C++20 std::format)
- Catch2 (optional for tests, but still WIP)
* Catch2 (optional for tests, but still WIP)
- cxxopts (header only)
* cxxopts (header only)
- SQLite 3
* SQLite 3
After installing all necessary dependencies, run the following commands:
```bash
# Start by cloning the repo, then...
git clone https://github.com/davidallendj/gdpm
cd https://github.com/davidallendj/gdpm
cd gdpm
export project_root=${pwd}
# ...if using Meson with Clang on Linux...
meson build
@ -70,6 +73,9 @@ cd build
cmake ..
make -j$(nproc)
# Easy build using predefined `compile` script
${project_root}/bin/compile.sh
# ...build using MinGW on Windows ???
# ...build using MSVC on Windows ???
# ...build using Clang on MacOS ???
@ -107,13 +113,13 @@ endif()
### Macro Definitions
| 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. |
| 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
@ -212,21 +218,21 @@ export PATH=$PATH:path/to/bin/gdpm
## 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.
* [ ] 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.
* [ ] Proper updating mechanism.
- [ ] Plugin integration into the editor.
* [ ] Plugin integration into the editor.
- [ ] Complete integration with the Asset API including moderation tools.
* [ ] Complete integration with the Asset API including moderation tools.
- [ ] Login and register for access to repositories.
* [ ] Login and register for access to repositories.
- [ ] Support for paid assets.
* [ ] Support for paid assets.
## Known Issues
- The `help` command does not should the command/options correctly. Commands do not use the double hypen, `--command` format. Commands should be use like `gdpm command --option` instead.
* The `help` command does not should the command/options correctly. Commands do not use the double hypen, `--command` format. Commands should be use like `gdpm command --option` instead.
## License

View file

@ -8,7 +8,7 @@
# CMake/ninja build system
mkdir -p build
CXX="zig c++ -target aarch64-linux-gnu" 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
ninja -C build -j $(nproc)

View file

@ -1,13 +1,28 @@
#include <string>
#include "log.hpp"
namespace gdpm::error_codes{
enum {
UNKNOWN = 0,
NOT_FOUND = 1,
};
inline std::string to_string(int error_code) {
return "";
}
};
namespace gdpm{
class error{
public:
error(int code = 0, const std::string& message = ""):
error(int code = 0, const std::string& message = "", bool print_message = false):
m_code(code), m_message(message)
{}
{
if(print_message)
print();
}
void set_code(int code) { m_code = code; }
void set_message(const std::string& message) { m_message = message; }
@ -15,6 +30,7 @@ namespace gdpm{
int get_code() const { return m_code; }
std::string get_message() const { return m_message; }
bool has_error() const { return m_code != 0; }
void print(){ log::println(GDPM_COLOR_LOG_ERROR "ERROR: {}" GDPM_COLOR_LOG_RESET, m_message); }
private:
int m_code;

View file

@ -1,6 +1,7 @@
#pragma once
#include "config.hpp"
#include "package_manager.hpp"
#include <cstdio>
#include <cxxopts.hpp>
#include <memory>
@ -47,13 +48,13 @@ namespace gdpm::package_manager{
remove,
update,
search,
p_export, /* reserved keyword */
list,
link,
clone,
clean,
sync,
add_remote,
delete_remote,
remote,
help,
none
};
@ -61,25 +62,35 @@ namespace gdpm::package_manager{
GDPM_DLL_EXPORT int initialize(int argc, char **argv);
GDPM_DLL_EXPORT int execute();
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 void list_information(const std::vector<std::string>& opts);
GDPM_DLL_EXPORT error export_packages(const std::string& path);
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);
GDPM_DLL_EXPORT void add_remote_repository(const std::string& repository, ssize_t offset = -1);
GDPM_DLL_EXPORT void delete_remote_repository(const std::string& repository);
GDPM_DLL_EXPORT void delete_remote_repository(ssize_t index);
GDPM_DLL_EXPORT cxxargs parse_arguments(int argc, char **argv);
GDPM_DLL_EXPORT void handle_arguments(const cxxargs& args);
/* Remote API */
GDPM_DLL_EXPORT void _handle_remote(const std::string& repository);
GDPM_DLL_EXPORT void remote_add_repository(const std::string& repository, ssize_t offset = -1);
GDPM_DLL_EXPORT void remote_remove_respository(const std::string& repository);
GDPM_DLL_EXPORT void remote_remove_respository(ssize_t index);
/* Auxiliary Functions */
GDPM_DLL_EXPORT cxxargs _parse_arguments(int argc, char **argv);
GDPM_DLL_EXPORT void _handle_arguments(const cxxargs& args);
GDPM_DLL_EXPORT void run_command(command_e command, const std::vector<std::string>& package_titles, const std::vector<std::string>& opts);
GDPM_DLL_EXPORT void print_package_list(const rapidjson::Document& json);
GDPM_DLL_EXPORT void print_package_list(const std::vector<package_info>& packages);
GDPM_DLL_EXPORT 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);
}

View file

@ -21,6 +21,7 @@
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h>
#include <system_error>
#include <future>
/*
@ -62,8 +63,8 @@ namespace gdpm::package_manager{
cache::create_package_database();
/* Run the rest of the program then exit */
cxxargs args = parse_arguments(argc, argv);
handle_arguments(args);
cxxargs args = _parse_arguments(argc, argv);
_handle_arguments(args);
return 0;
}
@ -114,9 +115,10 @@ namespace gdpm::package_manager{
/* Found nothing to install so there's nothing to do at this point. */
if(p_found.empty()){
log::error("No packages found to install.");
const char *message = "No packages found to install.";
log::error(message);
error.set_code(-1);
error.set_message("No packages found to install.");
error.set_message(message);
return error;
}
@ -134,108 +136,116 @@ namespace gdpm::package_manager{
using ss_pair = std::pair<std::string, std::string>;
std::vector<ss_pair> dir_pairs;
for(auto& p : p_found){
log::info("Fetching asset data for \"{}\"...", p.title);
std::vector<std::future<gdpm::error>> tasks;
for(auto& p : p_found){ // TODO: Execute each in parallel using coroutines??
/* TODO: Try fetching the data with all available remote sources until retrieved */
for(const auto& remote_url : config.remote_sources){
std::string url{remote_url}, package_dir, tmp_dir, tmp_zip;
// tasks.emplace_back(
// std::async(std::launch::async, [&p, &p_found, &error, &dir_pairs](){
url += rest_api::endpoints::GET_AssetId;
log::info("Fetching asset data for \"{}\"...", p.title);
/* Retrieve necessary asset data if it was found already in cache */
Document doc;
// log::debug("download_url: {}\ncategory: {}\ndescription: {}\nsupport_level: {}", p.download_url, p.category, p.description, p.support_level);
if(p.download_url.empty() || p.category.empty() || p.description.empty() || p.support_level.empty()){
params.verbose = config.verbose;
doc = rest_api::get_asset(url, p.asset_id, params);
if(doc.HasParseError() || doc.IsNull()){
log::println("");
log::error("Error parsing HTTP response. (error code: {})", doc.GetParseError());
error.set_code(doc.GetParseError());
return error;
/* TODO: Try fetching the data with all available remote sources until retrieved */
for(const auto& remote_url : config.remote_sources){
std::string url{remote_url}, package_dir, tmp_dir, tmp_zip;
url += rest_api::endpoints::GET_AssetId;
/* Retrieve necessary asset data if it was found already in cache */
// log::debug("download_url: {}\ncategory: {}\ndescription: {}\nsupport_level: {}", p.download_url, p.category, p.description, p.support_level);
Document doc;
bool is_valid = p.download_url.empty() || p.category.empty() || p.description.empty() || p.support_level.empty();
if(is_valid){
params.verbose = config.verbose;
doc = rest_api::get_asset(url, p.asset_id, params);
if(doc.HasParseError() || doc.IsNull()){
log::println("");
log::error("Error parsing HTTP response. (error code: {})", doc.GetParseError());
error.set_code(doc.GetParseError());
return error;
}
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{
/* 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 == 200){
log::println("Done.");
}else{
log::error("Something went wrong...(code {})", response.code);
error.set_code(response.code);
error.set_message("Error in HTTP response.");
return error;
}
}
dir_pairs.emplace_back(ss_pair(tmp_zip, package_dir + "/"));
p.is_installed = true;
p.install_path = package_dir;
}
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{
/* 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";
/* 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());
/* 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 == 200){
log::println("Done.");
}else{
log::error("Something went wrong...(code {})", response.code);
error.set_code(response.code);
error.set_message("Error in HTTP response.");
return error;
}
}
dir_pairs.emplace_back(ss_pair(tmp_zip, package_dir + "/"));
p.is_installed = true;
p.install_path = package_dir;
}
/* Update the cache data with information from */
log::info_n("Updating local asset data...");
cache::update_package_info(p_found);
log::println("done.");
// })
// );
}
/* 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;
}
@ -244,25 +254,12 @@ namespace gdpm::package_manager{
using namespace rapidjson;
using namespace std::filesystem;
error error;
if(package_titles.empty()){
std::string message("No packages to remove.");
log::println("");
log::error(message);
error.set_code(-1);
error.set_message(message);
return error;
}
/* Find the packages to remove if they're is_installed and show them to the user */
std::vector<package_info> p_cache = cache::get_package_info_by_title(package_titles);
if(p_cache.empty()){
std::string message("Could not find any packages to remove.");
log::println("");
constexpr const char *message("\nCould not find any packages to remove.");
log::error(message);
error.set_code(-1);
error.set_message(message);
return error;
return error(error_codes::NOT_FOUND, message, true);
}
/* Count number packages in cache flagged as is_installed. If there are none, then there's nothing to do. */
@ -272,12 +269,9 @@ namespace gdpm::package_manager{
});
if(p_count == 0){
std::string message("No packages to remove.");
log::println("");
constexpr const char *message("\nNo packages to remove.");
log::error(message);
error.set_code(-1);
error.set_message(message);
return error;
return error(error_codes::NOT_FOUND, message, true);
}
log::println("Packages to remove:");
@ -288,7 +282,7 @@ namespace gdpm::package_manager{
if(!skip_prompt){
if(!utils::prompt_user_yn("Do you want to remove these packages? (y/n)"))
return error;
return error();
}
log::info_n("Removing packages...");
@ -331,6 +325,18 @@ namespace gdpm::package_manager{
cache::update_package_info(p_cache);
log::println("done.");
return error();
}
/**
Removes all local packages.
*/
error remove_all_packages(){
/* Get the list of all packages to remove then remove */
std::vector<package_info> p_installed = cache::get_installed_packages();
std::vector<std::string> p_titles = get_package_titles(p_installed);
error error = remove_packages(p_titles);
return error;
}
@ -419,7 +425,29 @@ namespace gdpm::package_manager{
}
void list_information(const std::vector<std::string>& opts){
error export_packages(const std::string& path){
error error;
/* Get all installed package information for export */
std::vector<package_info> p_installed = cache::get_installed_packages();
std::vector<std::string> p_titles = get_package_titles(p_installed);
/* Build string of contents with one package title per line */
std::string output;
std::for_each(p_titles.begin(), p_titles.end(), [&output](const std::string& p){
output += p + "\n";
});
/* Write contents of installed packages in reusable format */
std::filesystem::path filepath(path);
std::ofstream of(filepath);
of << output;
return error;
}
std::vector<std::string> list_information(const std::vector<std::string>& opts, bool print_list){
using namespace rapidjson;
using namespace std::filesystem;
@ -427,16 +455,20 @@ namespace gdpm::package_manager{
const path path{config.packages_dir};
std::vector<package_info> p_installed = cache::get_installed_packages();
if(p_installed.empty())
return;
return get_package_titles(p_installed);
log::println("Installed packages:");
print_package_list(p_installed);
if(print_list)
print_package_list(p_installed);
return get_package_titles(p_installed);
}
else if(opts[0] == "remote-sources"){
print_remote_sources();
else if(opts[0] == "remote"){
if(print_list)
print_remote_sources();
return std::vector<std::string>();
}
else{
log::error("Unrecognized subcommand. Use either 'packages' or 'remote-sources' instead.");
return;
return std::vector<std::string>();
}
}
@ -550,7 +582,12 @@ namespace gdpm::package_manager{
}
void add_remote_repository(const std::string& repository, ssize_t offset){
void _handle_remote(const std::string& repository){
}
void remote_add_repository(const std::string& repository, ssize_t offset){
auto& s = config.remote_sources;
// auto iter = (offset > 0) ? s.begin() + offset : s.end() - offset;
// config.remote_sources.insert(iter, repository);
@ -558,7 +595,7 @@ namespace gdpm::package_manager{
}
void delete_remote_repository(const std::string& repository){
void remote_remove_respository(const std::string& repository){
auto& s = config.remote_sources;
s.erase(repository);
// std::erase(s, repository);
@ -569,7 +606,7 @@ namespace gdpm::package_manager{
/* TODO: Need to finish implementation...will do that when it's needed. */
void delete_remote_repository(size_t index){
void remote_remove_respository(size_t index){
auto& s = config.remote_sources;
// std::erase(s, index);
}
@ -595,7 +632,7 @@ namespace gdpm::package_manager{
}
cxxargs parse_arguments(int argc, char **argv){
cxxargs _parse_arguments(int argc, char **argv){
/* Parse command-line arguments using cxxopts */
cxxopts::Options options(
argv[0],
@ -609,14 +646,13 @@ namespace gdpm::package_manager{
("remove", "Remove a package or packages.", cxxopts::value<std::vector<std::string>>()->implicit_value(""), "<packages...>")
("update", "Update a package or packages. This will update all packages if no argument is provided.", cxxopts::value<std::vector<std::string>>()->implicit_value(""), "<packages...>")
("search", "Search for a package or packages.", cxxopts::value<std::vector<std::string>>(), "<packages...>")
("export", "Export list of packages", cxxopts::value<std::string>()->default_value("./gdpm-packages.txt"))
("list", "Show list of installed packages.")
("link", "Create a symlink (or shortcut) to target directory. Must be used with the `--path` argument.", cxxopts::value<std::vector<std::string>>(), "<packages...>")
("clone", "Clone packages into target directory. Must be used with the `--path` argument.", cxxopts::value<std::vector<std::string>>(), "<packages...>")
("clean", "Clean temporary downloaded files.")
("fetch", "Fetch asset data from remote sources.")
("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>")
("quick-remote", "One time remote source use. Source is not saved and override sources used in config.", cxxopts::value<std::vector<std::string>>(), "<url>")
("remote", "Set a source repository.", cxxopts::value<std::string>()->default_value(constants::AssetRepo), "<url>")
("h,help", "Print this message and exit.")
("version", "Show the current version and exit.")
;
@ -645,11 +681,14 @@ namespace gdpm::package_manager{
}
void handle_arguments(const cxxargs& args){
void _handle_arguments(const cxxargs& args){
const auto& result = args.result;
const auto& options = args.options;
/* Set option variables first to be used in functions below. */
if(result.count("help")){
log::println("{}", options.help());
}
if(result.count("config")){
config.path = result["config"].as<std::string>();
config::load(config.path, config);
@ -758,18 +797,19 @@ namespace gdpm::package_manager{
}
}
/* Catch arguments passed with or without dashes */
if(argv[0] == "install" || argv[0] == "--install") command = install;
else if (argv[0] == "remove" || argv[0] == "--remove") command = remove;
else if(argv[0] == "update" || argv[0] == "--update") command = update;
else if(argv[0] == "search" || argv[0] == "--search") command = search;
else if(argv[0] == "export" || argv[0] == "--export") command = p_export;
else if(argv[0] == "list" || argv[0] == "-ls") command = list;
else if(argv[0] == "link" || argv[0] == "--link") command = link;
else if(argv[0] == "clone" || argv[0] == "--clone") command = clone;
else if(argv[0] == "clean" || argv[0] == "--clean") command = clean;
else if(argv[0] == "sync" || argv[0] == "--sync") command = sync;
else if(argv[0] == "add-remote" || argv[0] == "--add-remote") command = add_remote;
else if(argv[0] == "delete-remote" || argv[0] == "--delete-remote") command = delete_remote;
else if(argv[0] == "help" || argv[0] == "-h" || argv[0] == "--help"){
else if(argv[0] == "remote" || argv[0] == "--remote") command = remote;
else if(argv[0] == "help" || argv[0] == "-h" || argv[0] == "--help" || argv[0].empty()){
log::println("{}", options.help());
}
else{
@ -785,14 +825,14 @@ namespace gdpm::package_manager{
case remove: remove_packages(package_titles, skip_prompt); break;
case update: update_packages(package_titles, skip_prompt); break;
case search: search_for_packages(package_titles, skip_prompt); break;
case list: list_information(package_titles); break;
case p_export: export_packages(opts[0]); 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;
case clean: clean_temporary(package_titles); break;
case sync: synchronize_database(package_titles); break;
case add_remote: add_remote_repository(opts[0], priority); break;
case delete_remote: delete_remote_repository(opts[0]); break;
case link: link_packages(package_titles, opts); break;
case clone: clone_packages(package_titles, opts); break;
case clean: clean_temporary(package_titles); break;
case sync: synchronize_database(package_titles); break;
case remote: _handle_remote(opts[0]); break;
case help: /* ...runs in handle_arguments() */ break;
case none: /* ...here to run with no command */ break;
}
@ -833,6 +873,15 @@ namespace gdpm::package_manager{
}
std::vector<std::string> get_package_titles(const std::vector<package_info> &packages){
std::vector<std::string> package_titles;
std::for_each(packages.begin(), packages.end(), [&package_titles](const package_info& p){
package_titles.emplace_back(p.title);
});
return package_titles;
}
void print_remote_sources(){
log::println("Remote sources:");
for(const auto& s : config.remote_sources){