mirror of
https://github.com/davidallendj/gdpm.git
synced 2025-12-19 19:17:01 -07:00
Implemented get command
This commit is contained in:
parent
d23bbce2e2
commit
766eabd5b2
6 changed files with 205 additions and 62 deletions
|
|
@ -43,21 +43,21 @@ Use `gdpm help` to see full list of commands.
|
|||
|
||||
```bash
|
||||
$ gdpm help
|
||||
Usage: gdpm [--help] [--verbose] {get,clean,clone,config,export,fetch,help,install,link,list,remote,remove,search,ui,update,version}
|
||||
Usage: gdpm [--help] [--verbose VAR] {clean,clone,config,export,fetch,get,help,install,link,list,remote,remove,search,ui,update,version}
|
||||
|
||||
Manage Godot engine assets from CLI
|
||||
|
||||
Optional arguments:
|
||||
-h, --help shows help message and exits
|
||||
-v, --verbose set verbosity level
|
||||
-v, --verbose set verbosity level [nargs=0..1] [default: false]
|
||||
|
||||
Subcommands:
|
||||
get add package to project
|
||||
clean clean package(s) temporary files
|
||||
clone clone package(s) to path
|
||||
config manage config properties
|
||||
export export install package(s) list
|
||||
fetch fetch and sync asset data
|
||||
get add package to project
|
||||
help show help message and exit
|
||||
install install package(s)
|
||||
link link package(s) to path
|
||||
|
|
@ -80,7 +80,7 @@ Currently, there is no dependency management as it is not needed yet. There are
|
|||
|
||||
## 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](#building-from-source)" section below).
|
||||
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](#building-from-source)` section below).
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -121,8 +121,9 @@ namespace gdpm::package {
|
|||
GDPM_DLL_EXPORT result_t<info_list> get_package_info(const opts_t& opts);
|
||||
GDPM_DLL_EXPORT result_t<title_list> get_package_titles(const info_list& packages);
|
||||
GDPM_DLL_EXPORT void clean_temporary(const config::context& config, const title_list& package_titles);
|
||||
GDPM_DLL_EXPORT params make_params(const var_args& args, const var_opts& opts);
|
||||
GDPM_DLL_EXPORT void read_file(title_list& package_titles, const path_list& paths);
|
||||
GDPM_DLL_EXPORT void read_file_inputs(title_list& package_titles, const path_list& paths);
|
||||
GDPM_DLL_EXPORT info_list find_cached_packages(const title_list& package_titles);
|
||||
GDPM_DLL_EXPORT info_list find_installed_packages(const title_list& package_titles);
|
||||
/* Dependency Management API */
|
||||
GDPM_DLL_EXPORT result_t<info_list> synchronize_database(const config::context& config, const title_list& package_titles);
|
||||
GDPM_DLL_EXPORT result_t<info_list> resolve_dependencies(const config::context& config, const title_list& package_titles);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace gdpm::package_manager {
|
|||
|
||||
enum class action_e{
|
||||
install,
|
||||
add,
|
||||
get,
|
||||
remove,
|
||||
update,
|
||||
search,
|
||||
|
|
|
|||
241
src/package.cpp
241
src/package.cpp
|
|
@ -10,6 +10,7 @@
|
|||
#include "remote.hpp"
|
||||
#include "types.hpp"
|
||||
#include "utils.hpp"
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <rapidjson/error/en.h>
|
||||
|
|
@ -40,43 +41,21 @@ namespace gdpm::package{
|
|||
*/
|
||||
|
||||
/* Append files from --file option */
|
||||
read_file(package_titles, params.input_files);
|
||||
read_file_inputs(package_titles, params.input_files);
|
||||
|
||||
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();
|
||||
}
|
||||
if(config.enable_sync || p_cache.empty()){
|
||||
result_t result = synchronize_database(config, package_titles);
|
||||
p_cache = result.unwrap_unsafe();
|
||||
}
|
||||
|
||||
|
||||
/* Match queried package titles with those found in cache. */
|
||||
log::debug("Searching for packages in cache...");
|
||||
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);
|
||||
}
|
||||
}
|
||||
package::info_list p_found = find_cached_packages(package_titles);
|
||||
|
||||
/* If size of package_titles == p_found, then all packages can be installed
|
||||
from cache and there's no need to query remote API. However, this will
|
||||
only installed the latest *local* version and will not sync with remote.
|
||||
|
||||
FIXME: This needs to also check if it is installed as well.
|
||||
*/
|
||||
// if(p_found.size() == package_titles.size()){
|
||||
// log::info("Found all packages stored in local cache.");
|
||||
// }
|
||||
|
|
@ -107,7 +86,7 @@ namespace gdpm::package{
|
|||
if(!config.remote_sources.contains(params.remote_source)){
|
||||
error error(
|
||||
constants::error::NOT_FOUND,
|
||||
"Remote resource not found in config."
|
||||
"Remote source not found in config."
|
||||
);
|
||||
log::error(error);
|
||||
return error;
|
||||
|
|
@ -228,8 +207,136 @@ namespace gdpm::package{
|
|||
title_list& package_titles,
|
||||
const params& params
|
||||
){
|
||||
/* Install packages in local project instead of package database.
|
||||
This will not cache the package information in the cache database. */
|
||||
using namespace rapidjson;
|
||||
|
||||
/* Download and install package(s) in local project without storing
|
||||
package info in the package database. This will check for packages stored
|
||||
in local cache first. */
|
||||
result_t result = cache::get_package_info_by_title(package_titles);
|
||||
package::info_list p_found = {};
|
||||
package::info_list p_cache = result.unwrap_unsafe();
|
||||
log::debug("Searching for packages in cache...");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Install the ones found from cache first. */
|
||||
|
||||
/* Check if provided param is in remote sources*/
|
||||
if(!config.remote_sources.contains(params.remote_source)){
|
||||
error error(
|
||||
constants::error::NOT_FOUND,
|
||||
"Remote source not found in config."
|
||||
);
|
||||
log::error(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Install the other packages from remte source. */
|
||||
std::vector<string_pair> dir_pairs;
|
||||
task_list tasks;
|
||||
rest_api::request_params 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);
|
||||
string url{config.remote_sources.at(params.remote_source) + rest_api::endpoints::GET_AssetId};
|
||||
string package_dir, tmp_dir, tmp_zip;
|
||||
|
||||
/* Retrieve necessary asset data if it was found already in cache */
|
||||
Document doc;
|
||||
bool is_missing_data = p.download_url.empty() || p.category.empty() || p.description.empty() || p.support_level.empty();
|
||||
if(is_missing_data){
|
||||
doc = rest_api::get_asset(url, p.asset_id, rest_api_params);
|
||||
if(doc.HasParseError() || doc.IsNull()){
|
||||
return log::error_rc(error(
|
||||
constants::error::JSON_ERR,
|
||||
std::format("Error parsing JSON: {}", GetParseError_En(doc.GetParseError()))
|
||||
));
|
||||
}
|
||||
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::info("Found asset data found for \"{}\"", p.title);
|
||||
}
|
||||
|
||||
/* Set directory and temp paths for storage */
|
||||
package_dir = std::filesystem::current_path().string() + "/" + p.title;//config.packages_dir + "/" + p.title;
|
||||
tmp_dir = std::filesystem::current_path().string() + "/" + p.title + ".tmp";
|
||||
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::info("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);
|
||||
http::context http;
|
||||
http::response response = http.download_file(p.download_url, tmp_zip);
|
||||
if(response.code == http::OK){
|
||||
log::println("Done.");
|
||||
}else{
|
||||
return log::error_rc(error(
|
||||
constants::error::HTTP_RESPONSE_ERR,
|
||||
std::format("HTTP Error: {}", response.code)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
dir_pairs.emplace_back(string_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){
|
||||
int ec = utils::extract_zip(p.first.c_str(), p.second.c_str());
|
||||
if(ec){
|
||||
log::error_rc(error(
|
||||
constants::error::LIBZIP_ERR,
|
||||
std::format("libzip returned an error code {}", ec)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove temporary download archive */
|
||||
for(const auto& p : p_found){
|
||||
string tmp_zip = std::filesystem::current_path().string()
|
||||
+ "/" + p.title + ".tmp.zip";
|
||||
std::filesystem::remove_all(tmp_zip);
|
||||
}
|
||||
}
|
||||
return error();
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +351,7 @@ namespace gdpm::package{
|
|||
|
||||
|
||||
/* Append package titles from --file option */
|
||||
read_file(package_titles, params.input_files);
|
||||
read_file_inputs(package_titles, params.input_files);
|
||||
|
||||
/* 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);
|
||||
|
|
@ -406,8 +513,6 @@ namespace gdpm::package{
|
|||
return error();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
error search(
|
||||
const config::context& config,
|
||||
|
|
@ -610,12 +715,10 @@ namespace gdpm::package{
|
|||
}
|
||||
|
||||
if(p_found.empty()){
|
||||
error error(
|
||||
return log::error_rc(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 */
|
||||
|
|
@ -657,6 +760,7 @@ namespace gdpm::package{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void print_list(const rapidjson::Document& json){
|
||||
for(const auto& o : json["result"].GetArray()){
|
||||
log::println(
|
||||
|
|
@ -679,6 +783,7 @@ namespace gdpm::package{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void print_table(const info_list& packages){
|
||||
using namespace tabulate;
|
||||
Table table;
|
||||
|
|
@ -756,7 +861,7 @@ namespace gdpm::package{
|
|||
const title_list& package_titles
|
||||
){
|
||||
if(package_titles.empty()){
|
||||
log::info("Cleaned all temporary files.");
|
||||
log::info("No temporary files found to clean.");
|
||||
std::filesystem::remove_all(config.tmp_dir);
|
||||
}
|
||||
/* Find the path of each packages is_installed then delete temporaries */
|
||||
|
|
@ -780,17 +885,6 @@ namespace gdpm::package{
|
|||
if(o.count(k)){ p = std::get<T>(o.at(k)); }
|
||||
}
|
||||
|
||||
params make_params(
|
||||
const var_args& args,
|
||||
const var_opts& opts
|
||||
){
|
||||
params p;
|
||||
set_if_key_exists(opts, "remote-source", p.remote_source);
|
||||
// set_if_key_exists(opts, "install-method", p.install_method);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
result_t<info_list> synchronize_database(
|
||||
const config::context& config,
|
||||
|
|
@ -899,7 +993,8 @@ namespace gdpm::package{
|
|||
return result_t(p_deps, error());
|
||||
}
|
||||
|
||||
void read_file(
|
||||
|
||||
void read_file_inputs(
|
||||
title_list& package_titles,
|
||||
const path_list& paths
|
||||
){
|
||||
|
|
@ -914,6 +1009,52 @@ namespace gdpm::package{
|
|||
}
|
||||
}
|
||||
|
||||
info_list find_cached_packages(const title_list& package_titles){
|
||||
/* Download and install package(s) in local project without storing
|
||||
package info in the package database. This will check for packages stored
|
||||
in local cache first. */
|
||||
result_t result = cache::get_package_info_by_title(package_titles);
|
||||
package::info_list p_found = {};
|
||||
package::info_list p_cache = result.unwrap_unsafe();
|
||||
log::debug("Searching for packages in cache...");
|
||||
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);
|
||||
}
|
||||
}
|
||||
return p_found;
|
||||
}
|
||||
|
||||
|
||||
info_list find_installed_packages(const title_list& package_titles){
|
||||
result_t result = cache::get_package_info_by_title(package_titles);
|
||||
package::info_list p_installed = {};
|
||||
package::info_list p_cache = result.unwrap_unsafe();
|
||||
log::debug("Searching for installed packages in cache...");
|
||||
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 && p.is_installed;
|
||||
}
|
||||
);
|
||||
if(found != p_cache.end()){
|
||||
p_installed.emplace_back(*found);
|
||||
}
|
||||
}
|
||||
|
||||
return p_installed;
|
||||
}
|
||||
|
||||
|
||||
string to_json(
|
||||
const info& info,
|
||||
bool pretty_pretty
|
||||
|
|
|
|||
|
|
@ -398,12 +398,12 @@ namespace gdpm::package_manager{
|
|||
set_if_used(install_command, config.timeout, "timeout");
|
||||
}
|
||||
else if(program.is_subcommand_used(get_command)){
|
||||
action = action_e::add;
|
||||
action = action_e::get;
|
||||
package_titles = get_values_from_parser(get_command);
|
||||
set_if_used(get_command, params.remote_source, "remote");
|
||||
set_if_used(get_command, config.jobs, "jobs");
|
||||
set_if_used(get_command, config.skip_prompt, "skip-prompt");
|
||||
set_if_used(get_command, params.input_files, "files");
|
||||
set_if_used(get_command, params.input_files, "file");
|
||||
}
|
||||
else if(program.is_subcommand_used(remove_command)){
|
||||
action = action_e::remove;
|
||||
|
|
@ -545,7 +545,7 @@ namespace gdpm::package_manager{
|
|||
|
||||
switch(action){
|
||||
case action_e::install: package::install(config, package_titles, params); break;
|
||||
case action_e::add: package::get(config, package_titles, params);
|
||||
case action_e::get: package::get(config, package_titles, params); break;
|
||||
case action_e::remove: package::remove(config, package_titles, params); break;
|
||||
case action_e::update: package::update(config, package_titles, params); break;
|
||||
case action_e::search: package::search(config, package_titles, params); break;
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ namespace gdpm::utils{
|
|||
if(total_download != 0){
|
||||
// double percent = std::floor((current_downloaded / (total_download)) * 100);
|
||||
bar.set_option(option::MaxProgress{total_download});
|
||||
// bar.set_option(option::HideBarWhenComplete{false});
|
||||
bar.set_progress(current_downloaded);
|
||||
bar.set_option(option::PostfixText{
|
||||
convert_size(current_downloaded) + " / " +
|
||||
|
|
@ -401,8 +402,8 @@ namespace gdpm::utils{
|
|||
}
|
||||
} else {
|
||||
if(bar_unknown.is_completed()){
|
||||
bar.set_option(option::PrefixText{"Download completed."});
|
||||
bar.mark_as_completed();
|
||||
bar_unknown.set_option(option::PrefixText{"Download completed."});
|
||||
bar_unknown.mark_as_completed();
|
||||
} else {
|
||||
bar.tick();
|
||||
bar_unknown.set_option(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue