diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ad2516..11173b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ file(WRITE "${generatedFilesDir}/project_version_from_cmake.hpp" "constexpr const char* project_version = \"${PROJECT_VERSION}\";\n") add_executable(cse + "src/cache.hpp" + "src/cache.cpp" + "src/clean_cache.hpp" + "src/clean_cache.cpp" "src/cli_application.hpp" "src/cli_application.cpp" "src/console_utils.hpp" diff --git a/src/cache.cpp b/src/cache.cpp new file mode 100644 index 0000000..9a8158e --- /dev/null +++ b/src/cache.cpp @@ -0,0 +1,93 @@ +#include "cache.hpp" + +#include +#include +#include + +#include +#include +#include + + +namespace +{ + +// A wrapper for `std::getenv()` that checks for both null and emptiness, +// and logs a debug message and returns null in either case. +const char* getenv(const char* variableName) +{ + const auto e = std::getenv(variableName); + if (e && *e) return e; + BOOST_LOG_SEV(cse::log::logger(), cse::log::debug) + << "Environment variable '" << variableName << "' not set."; + return nullptr; +} + +// Obtain the (platform-specific) application cache path for the current user. +std::optional user_cache_directory_path() +{ +#if defined(_WIN32) + if (const auto localAppData = getenv("LocalAppData")) { + return boost::filesystem::path(localAppData); + } + if (const auto userProfile = getenv("UserProfile")) { + return boost::filesystem::path(userProfile) / "AppData" / "Local"; + } + +#elif defined(__APPLE__) + if (const auto home = getenv("HOME")) { + return boost::filesystem::path(home) / "Library" / "Caches"; + } + +#else // some other UNIX-like system + if (const auto xdgCacheHome = getenv("XDG_CACHE_HOME")) { + return boost::filesystem::path(xdgCacheHome); + } + if (const auto home = getenv("HOME")) { + return boost::filesystem::path(home) / ".cache"; + } + +#endif + return std::nullopt; +} + +// Returns the (platform-specific) user cache path for this application. +std::optional cache_directory_path() +{ + if (const auto userCache = user_cache_directory_path()) { + return *userCache / "cse"; + } else { + return std::nullopt; + } +} + +} // namespace + + +std::shared_ptr caching_model_uri_resolver() +{ + if (const auto cachePath = cache_directory_path()) { + const auto cache = std::make_shared(*cachePath); + BOOST_LOG_SEV(cse::log::logger(), cse::log::info) + << "Using cache directory: " << *cachePath; + return cse::default_model_uri_resolver(cache); + } else { + BOOST_LOG_SEV(cse::log::logger(), cse::log::warning) + << "Unable to determine user cache directory; caching is disabled."; + return cse::default_model_uri_resolver(); + } +} + + +void clean_cache() +{ + if (const auto cachePath = cache_directory_path()) { + const auto cache = std::make_shared(*cachePath); + BOOST_LOG_SEV(cse::log::logger(), cse::log::info) + << "Cleaning cache directory: " << *cachePath; + cache->cleanup(); + } else { + throw std::runtime_error( + "Unable to determine user cache directory; cannot delete it."); + } +} diff --git a/src/cache.hpp b/src/cache.hpp new file mode 100644 index 0000000..153f245 --- /dev/null +++ b/src/cache.hpp @@ -0,0 +1,17 @@ +#ifndef CSECLI_CACHE_HPP +#define CSECLI_CACHE_HPP + +#include + +#include + + +/// Returns a caching model URI resolver. +std::shared_ptr caching_model_uri_resolver(); + + +/// Removes unused data from the application cache directory. +void clean_cache(); + + +#endif // header guard diff --git a/src/clean_cache.cpp b/src/clean_cache.cpp new file mode 100644 index 0000000..8a37339 --- /dev/null +++ b/src/clean_cache.cpp @@ -0,0 +1,20 @@ +#include "clean_cache.hpp" + +#include "cache.hpp" + + +void clean_cache_subcommand::setup_options( + boost::program_options::options_description& /*options*/, + boost::program_options::options_description& /*positionalOptions*/, + boost::program_options::positional_options_description& /*positions*/) + const noexcept +{ + // no options +} + + +int clean_cache_subcommand::run(const boost::program_options::variables_map& /*args*/) const +{ + clean_cache(); + return 0; +} diff --git a/src/clean_cache.hpp b/src/clean_cache.hpp new file mode 100644 index 0000000..3180c7c --- /dev/null +++ b/src/clean_cache.hpp @@ -0,0 +1,49 @@ +#ifndef CSECLI_CLEAN_CACHE_HPP +#define CSECLI_CLEAN_CACHE_HPP + +#include "cli_application.hpp" + + +/// The `clean-cache` subcommand. +class clean_cache_subcommand : public cli_subcommand +{ +public: + std::string name() const noexcept override + { + return "clean-cache"; + } + + std::string brief_description() const noexcept override + { + return "Removes unused data from the program cache"; + } + + std::string long_description() const noexcept override + { + return "This command removes unused files from the directory that " + "contains cached CSE data for the current user.\n" + "\n" + "The primary use for the cache is to unpack FMUs in a " + "persistent location, " + "so they don't have to be unpacked over and over for each run. " + "This can be a major time saver, " + "especially when working with large FMUs. " + "Over time, however, the cache can grow to take up " + "a significant amount of disk space.\n" + "\n" + "This command allows for safe removal of files from the cache. " + "It will remove all files that are not currently in use by a " + "CSE process."; + } + + void setup_options( + boost::program_options::options_description& options, + boost::program_options::options_description& positionalOptions, + boost::program_options::positional_options_description& positions) + const noexcept override; + + int run(const boost::program_options::variables_map& args) const override; +}; + + +#endif diff --git a/src/inspect.cpp b/src/inspect.cpp index 4ea07f7..929cb2a 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -1,5 +1,6 @@ #include "inspect.hpp" +#include "cache.hpp" #include "tools.hpp" #include @@ -69,7 +70,7 @@ int inspect_subcommand::run(const boost::program_options::variables_map& args) c currentPath += boost::filesystem::path::preferred_separator; const auto baseUri = cse::path_to_file_uri(currentPath); const auto uriReference = to_uri(args["uri_or_path"].as()); - const auto uriResolver = cse::default_model_uri_resolver(); + const auto uriResolver = caching_model_uri_resolver(); const auto model = uriResolver->lookup_model(baseUri, uriReference); print_model_description(*model->description()); if (args.count("no-vars") == 0) { diff --git a/src/main.cpp b/src/main.cpp index 3b18cef..08e3979 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "clean_cache.hpp" #include "cli_application.hpp" #include "inspect.hpp" #include "logging_options.hpp" @@ -31,6 +32,7 @@ int main(int argc, char const* const* argv) "The Core Simulation Environment is free and open-source software for running distributed co-simulations."); app.add_global_options(std::make_unique()); app.add_global_options(std::make_unique("CSE CLI", project_version)); + app.add_subcommand(std::make_unique()); app.add_subcommand(std::make_unique()); app.add_subcommand(std::make_unique()); app.add_subcommand(std::make_unique()); diff --git a/src/run.cpp b/src/run.cpp index 744a576..7cad727 100644 --- a/src/run.cpp +++ b/src/run.cpp @@ -1,5 +1,6 @@ #include "run.hpp" +#include "cache.hpp" #include "run_common.hpp" #include @@ -170,7 +171,7 @@ int run_subcommand::run(const boost::program_options::variables_map& args) const const auto systemStructurePath = boost::filesystem::path(args["system_structure_path"].as()); - const auto uriResolver = cse::default_model_uri_resolver(); + const auto uriResolver = caching_model_uri_resolver(); auto execution = load_system_structure( systemStructurePath, *uriResolver, diff --git a/src/run_single.cpp b/src/run_single.cpp index 914a2f2..5eee691 100644 --- a/src/run_single.cpp +++ b/src/run_single.cpp @@ -3,6 +3,7 @@ #endif #include "run_single.hpp" +#include "cache.hpp" #include "run_common.hpp" #include "tools.hpp" @@ -253,7 +254,7 @@ int run_single_subcommand::run(const boost::program_options::variables_map& args currentPath += boost::filesystem::path::preferred_separator; const auto baseUri = cse::path_to_file_uri(currentPath); const auto uriReference = to_uri(args["uri_or_path"].as()); - const auto uriResolver = cse::default_model_uri_resolver(); + const auto uriResolver = caching_model_uri_resolver(); const auto model = uriResolver->lookup_model(baseUri, uriReference); std::optional initialValues;