diff --git a/engine/commands/chat_completion_cmd.cc b/engine/commands/chat_completion_cmd.cc index 1ebaa8b1a..5a92c4b95 100644 --- a/engine/commands/chat_completion_cmd.cc +++ b/engine/commands/chat_completion_cmd.cc @@ -1,6 +1,7 @@ #include "chat_completion_cmd.h" #include "httplib.h" +#include "config/yaml_config.h" #include "cortex_upd_cmd.h" #include "database/models.h" #include "model_status_cmd.h" @@ -8,7 +9,6 @@ #include "server_start_cmd.h" #include "trantor/utils/Logger.h" #include "utils/logging_utils.h" -#include "config/yaml_config.h" namespace commands { namespace { @@ -41,6 +41,8 @@ struct ChunkParser { void ChatCompletionCmd::Exec(const std::string& host, int port, const std::string& model_handle, std::string msg) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; try { @@ -49,7 +51,10 @@ void ChatCompletionCmd::Exec(const std::string& host, int port, CLI_LOG("Error: " + model_entry.error()); return; } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto mc = yaml_handler.GetModelConfig(); Exec(host, port, model_handle, mc, std::move(msg)); } catch (const std::exception& e) { diff --git a/engine/commands/model_get_cmd.cc b/engine/commands/model_get_cmd.cc index 47931ee89..3cf46bd84 100644 --- a/engine/commands/model_get_cmd.cc +++ b/engine/commands/model_get_cmd.cc @@ -11,6 +11,8 @@ namespace commands { void ModelGetCmd::Exec(const std::string& model_handle) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; try { @@ -19,7 +21,10 @@ void ModelGetCmd::Exec(const std::string& model_handle) { CLI_LOG("Error: " + model_entry.error()); return; } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto model_config = yaml_handler.GetModelConfig(); std::cout << model_config.ToString() << std::endl; diff --git a/engine/commands/model_import_cmd.cc b/engine/commands/model_import_cmd.cc index c12af5054..15fc6f897 100644 --- a/engine/commands/model_import_cmd.cc +++ b/engine/commands/model_import_cmd.cc @@ -14,6 +14,8 @@ ModelImportCmd::ModelImportCmd(std::string model_handle, std::string model_path) model_path_(std::move(model_path)) {} void ModelImportCmd::Exec() { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; config::GGUFHandler gguf_handler; config::YamlHandler yaml_handler; cortex::db::Models modellist_utils_obj; @@ -22,10 +24,13 @@ void ModelImportCmd::Exec() { std::filesystem::path("imported") / std::filesystem::path(model_handle_ + ".yml")) .string(); - cortex::db::ModelEntry model_entry{ - model_handle_, "local", "imported", - model_yaml_path, model_handle_}; try { + // Use relative path for model_yaml_path. In case of import, we use absolute path for model + auto yaml_rel_path = + fmu::ToRelativeCortexDataPath(fs::path(model_yaml_path)); + cortex::db::ModelEntry model_entry{model_handle_, "local", "imported", + yaml_rel_path.string(), model_handle_}; + std::filesystem::create_directories( std::filesystem::path(model_yaml_path).parent_path()); gguf_handler.Parse(model_path_); diff --git a/engine/commands/model_list_cmd.cc b/engine/commands/model_list_cmd.cc index 7ac538cdf..a340c999a 100644 --- a/engine/commands/model_list_cmd.cc +++ b/engine/commands/model_list_cmd.cc @@ -10,6 +10,8 @@ namespace commands { void ModelListCmd::Exec() { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; auto models_path = file_manager_utils::GetModelsContainerPath(); cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; @@ -29,7 +31,10 @@ void ModelListCmd::Exec() { // auto model_entry = modellist_handler.GetModelInfo(model_handle); try { count += 1; - yaml_handler.ModelConfigFromFile(model_entry.path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.path_to_model_yaml)) + .string()); auto model_config = yaml_handler.GetModelConfig(); table.add_row({std::to_string(count), model_entry.model, model_entry.model_alias, model_config.engine, diff --git a/engine/commands/model_upd_cmd.cc b/engine/commands/model_upd_cmd.cc index ea10d2d95..0864c227a 100644 --- a/engine/commands/model_upd_cmd.cc +++ b/engine/commands/model_upd_cmd.cc @@ -1,5 +1,5 @@ #include "model_upd_cmd.h" - +#include "utils/file_manager_utils.h" #include "utils/logging_utils.h" namespace commands { @@ -9,13 +9,17 @@ ModelUpdCmd::ModelUpdCmd(std::string model_handle) void ModelUpdCmd::Exec( const std::unordered_map& options) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; try { auto model_entry = model_list_utils_.GetModelInfo(model_handle_); if (model_entry.has_error()) { CLI_LOG("Error: " + model_entry.error()); return; } - yaml_handler_.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + auto yaml_fp = fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)); + yaml_handler_.ModelConfigFromFile(yaml_fp.string()); model_config_ = yaml_handler_.GetModelConfig(); for (const auto& [key, value] : options) { @@ -25,7 +29,7 @@ void ModelUpdCmd::Exec( } yaml_handler_.UpdateModelConfig(model_config_); - yaml_handler_.WriteYamlFile(model_entry.value().path_to_model_yaml); + yaml_handler_.WriteYamlFile(yaml_fp.string()); CLI_LOG("Successfully updated model ID '" + model_handle_ + "'!"); } catch (const std::exception& e) { CLI_LOG("Failed to update model with model ID '" + model_handle_ + diff --git a/engine/commands/run_cmd.cc b/engine/commands/run_cmd.cc index 8d2cdc77e..1bf85afa6 100644 --- a/engine/commands/run_cmd.cc +++ b/engine/commands/run_cmd.cc @@ -32,12 +32,17 @@ void RunCmd::Exec(bool chat_flag) { } try { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; auto model_entry = modellist_handler.GetModelInfo(*model_id); if (model_entry.has_error()) { CLI_LOG("Error: " + model_entry.error()); return; } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto mc = yaml_handler.GetModelConfig(); // Check if engine existed. If not, download it diff --git a/engine/config/yaml_config.cc b/engine/config/yaml_config.cc index 104fa3b5e..bfefd2d7f 100644 --- a/engine/config/yaml_config.cc +++ b/engine/config/yaml_config.cc @@ -3,9 +3,9 @@ #include #include #include -using namespace std; #include "utils/format_utils.h" +#include "utils/file_manager_utils.h" #include "yaml_config.h" namespace config { // Method to read YAML file @@ -14,6 +14,8 @@ void YamlHandler::Reset() { yaml_node_.reset(); }; void YamlHandler::ReadYamlFile(const std::string& file_path) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; try { yaml_node_ = YAML::LoadFile(file_path); // incase of model.yml file, we don't have files yet, create them @@ -24,8 +26,9 @@ void YamlHandler::ReadYamlFile(const std::string& file_path) { std::vector v; if (yaml_node_["engine"] && yaml_node_["engine"].as() == "cortex.llamacpp") { - // TODO: change prefix to models:// with source from cortexso - v.emplace_back(s.substr(0, s.find_last_of('/')) + "/model.gguf"); + auto abs_path = s.substr(0, s.find_last_of('/')) + "/model.gguf"; + auto rel_path = fmu::ToRelativeCortexDataPath(fs::path(abs_path)); + v.emplace_back(rel_path.string()); } else { v.emplace_back(s.substr(0, s.find_last_of('/'))); } @@ -286,9 +289,7 @@ void YamlHandler::WriteYamlFile(const std::string& file_path) const { outFile << "version: " << yaml_node_["version"].as() << "\n"; } if (yaml_node_["files"] && yaml_node_["files"].size()) { - outFile << "files: # can be universal protocol (models://) " - "OR absolute local file path (file://) OR https remote URL " - "(https://)\n"; + outFile << "files: # Can be relative OR absolute local file path\n"; for (const auto& source : yaml_node_["files"]) { outFile << " - " << source << "\n"; } diff --git a/engine/controllers/models.cc b/engine/controllers/models.cc index b4277ac00..739f614c9 100644 --- a/engine/controllers/models.cc +++ b/engine/controllers/models.cc @@ -59,6 +59,8 @@ void Models::PullModel(const HttpRequestPtr& req, void Models::ListModel( const HttpRequestPtr& req, std::function&& callback) const { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; Json::Value ret; ret["object"] = "list"; Json::Value data(Json::arrayValue); @@ -73,8 +75,10 @@ void Models::ListModel( for (const auto& model_entry : list_entry.value()) { // auto model_entry = modellist_handler.GetModelInfo(model_handle); try { - - yaml_handler.ModelConfigFromFile(model_entry.path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.path_to_model_yaml)) + .string()); auto model_config = yaml_handler.GetModelConfig(); Json::Value obj = model_config.ToJson(); @@ -106,6 +110,8 @@ void Models::ListModel( void Models::GetModel(const HttpRequestPtr& req, std::function&& callback, const std::string& model_id) const { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; LOG_DEBUG << "GetModel, Model handle: " << model_id; Json::Value ret; ret["object"] = "list"; @@ -125,7 +131,10 @@ void Models::GetModel(const HttpRequestPtr& req, callback(resp); return; } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto model_config = yaml_handler.GetModelConfig(); Json::Value obj = model_config.ToJson(); @@ -137,8 +146,8 @@ void Models::GetModel(const HttpRequestPtr& req, resp->setStatusCode(k200OK); callback(resp); } catch (const std::exception& e) { - std::string message = "Fail to get model information with ID '" + - model_id + "': " + e.what(); + std::string message = + "Fail to get model information with ID '" + model_id + "': " + e.what(); LOG_ERROR << message; ret["data"] = data; ret["result"] = "Fail to get model information"; @@ -171,16 +180,20 @@ void Models::DeleteModel(const HttpRequestPtr& req, void Models::UpdateModel(const HttpRequestPtr& req, std::function&& callback, const std::string& model_id) const { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; auto json_body = *(req->getJsonObject()); try { cortex::db::Models model_list_utils; auto model_entry = model_list_utils.GetModelInfo(model_id); config::YamlHandler yaml_handler; - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + auto yaml_fp = fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)); + yaml_handler.ModelConfigFromFile(yaml_fp.string()); config::ModelConfig model_config = yaml_handler.GetModelConfig(); model_config.FromJson(json_body); yaml_handler.UpdateModelConfig(model_config); - yaml_handler.WriteYamlFile(model_entry.value().path_to_model_yaml); + yaml_handler.WriteYamlFile(yaml_fp.string()); std::string message = "Successfully update model ID '" + model_id + "': " + json_body.toStyledString(); LOG_INFO << message; @@ -210,6 +223,8 @@ void Models::UpdateModel(const HttpRequestPtr& req, void Models::ImportModel( const HttpRequestPtr& req, std::function&& callback) const { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; if (!http_util::HasFieldInReq(req, callback, "model") || !http_util::HasFieldInReq(req, callback, "modelPath")) { return; @@ -219,14 +234,18 @@ void Models::ImportModel( config::GGUFHandler gguf_handler; config::YamlHandler yaml_handler; cortex::db::Models modellist_utils_obj; - std::string model_yaml_path = (file_manager_utils::GetModelsContainerPath() / std::filesystem::path("imported") / std::filesystem::path(modelHandle + ".yml")) .string(); - cortex::db::ModelEntry model_entry{modelHandle, "local", "imported", - model_yaml_path, modelHandle}; + try { + // Use relative path for model_yaml_path. In case of import, we use absolute path for model + auto yaml_rel_path = + fmu::ToRelativeCortexDataPath(fs::path(model_yaml_path)); + cortex::db::ModelEntry model_entry{modelHandle, "local", "imported", + yaml_rel_path.string(), modelHandle}; + std::filesystem::create_directories( std::filesystem::path(model_yaml_path).parent_path()); gguf_handler.Parse(modelPath); @@ -295,13 +314,13 @@ void Models::SetModelAlias( if (result.has_error()) { std::string message = result.error(); LOG_ERROR << message; - Json::Value ret; - ret["result"] = "Set alias failed!"; - ret["modelHandle"] = model_handle; - ret["message"] = message; - auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); - resp->setStatusCode(k400BadRequest); - callback(resp); + Json::Value ret; + ret["result"] = "Set alias failed!"; + ret["modelHandle"] = model_handle; + ret["message"] = message; + auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); + resp->setStatusCode(k400BadRequest); + callback(resp); } else { if (result.value()) { std::string message = "Successfully set model alias '" + model_alias + diff --git a/engine/main.cc b/engine/main.cc index e5cac996b..985042a5d 100644 --- a/engine/main.cc +++ b/engine/main.cc @@ -46,8 +46,8 @@ void RunServer() { std::filesystem::path(config.logFolderPath) / std::filesystem::path(cortex_utils::logs_folder)); trantor::FileLogger asyncFileLogger; - asyncFileLogger.setFileName(config.logFolderPath + "/" + - cortex_utils::logs_base_name); + asyncFileLogger.setFileName((std::filesystem::path(config.logFolderPath) / + std::filesystem::path(cortex_utils::logs_base_name)).string()); asyncFileLogger.setMaxLines(config.maxLogLines); // Keep last 100000 lines asyncFileLogger.startLogging(); trantor::Logger::setOutputFunction( diff --git a/engine/services/download_service.cc b/engine/services/download_service.cc index 35444c238..2487d6336 100644 --- a/engine/services/download_service.cc +++ b/engine/services/download_service.cc @@ -173,6 +173,7 @@ cpp::result DownloadService::Download( CTL_INF("Existing file size: " << download_item.downloadUrl << " - " << download_item.localPath.string() << " - " << existing_file_size); + CTL_INF("Download item size: " << download_item.bytes.value()); auto missing_bytes = download_item.bytes.value() - existing_file_size; if (missing_bytes > 0) { CLI_LOG("Found unfinished download! Additional " diff --git a/engine/services/model_service.cc b/engine/services/model_service.cc index 6342c8a0a..080b88a88 100644 --- a/engine/services/model_service.cc +++ b/engine/services/model_service.cc @@ -17,14 +17,19 @@ namespace { void ParseGguf(const DownloadItem& ggufDownloadItem, std::optional author) { - + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; config::GGUFHandler gguf_handler; config::YamlHandler yaml_handler; gguf_handler.Parse(ggufDownloadItem.localPath.string()); + config::ModelConfig model_config = gguf_handler.GetModelConfig(); model_config.id = ggufDownloadItem.localPath.parent_path().filename().string(); - model_config.files = {ggufDownloadItem.localPath.string()}; + // use relative path for files + auto file_rel_path = + fmu::ToRelativeCortexDataPath(fs::path(ggufDownloadItem.localPath)); + model_config.files = {file_rel_path.string()}; model_config.model = ggufDownloadItem.id; yaml_handler.UpdateModelConfig(model_config); @@ -39,12 +44,15 @@ void ParseGguf(const DownloadItem& ggufDownloadItem, auto branch = url_obj.pathParams[3]; CTL_INF("Adding model to modellist with branch: " << branch); + auto rel = file_manager_utils::ToRelativeCortexDataPath(yaml_name); + CTL_INF("path_to_model_yaml: " << rel.string()); + auto author_id = author.has_value() ? author.value() : "cortexso"; cortex::db::Models modellist_utils_obj; cortex::db::ModelEntry model_entry{.model = ggufDownloadItem.id, .author_repo_id = author_id, .branch_name = branch, - .path_to_model_yaml = yaml_name.string(), + .path_to_model_yaml = rel.string(), .model_alias = ggufDownloadItem.id}; modellist_utils_obj.AddModelEntry(model_entry, true); } @@ -295,13 +303,16 @@ cpp::result ModelService::DownloadModelFromCortexso( yaml_handler.UpdateModelConfig(mc); yaml_handler.WriteYamlFile(model_yml_item->localPath.string()); + auto rel = + file_manager_utils::ToRelativeCortexDataPath(model_yml_item->localPath); + CTL_INF("path_to_model_yaml: " << rel.string()); + cortex::db::Models modellist_utils_obj; - cortex::db::ModelEntry model_entry{ - .model = model_id, - .author_repo_id = "cortexso", - .branch_name = branch, - .path_to_model_yaml = model_yml_item->localPath.string(), - .model_alias = model_id}; + cortex::db::ModelEntry model_entry{.model = model_id, + .author_repo_id = "cortexso", + .branch_name = branch, + .path_to_model_yaml = rel.string(), + .model_alias = model_id}; modellist_utils_obj.AddModelEntry(model_entry); }; @@ -353,7 +364,8 @@ ModelService::DownloadHuggingFaceGgufModel(const std::string& author, cpp::result ModelService::DeleteModel( const std::string& model_handle) { - + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; @@ -363,20 +375,24 @@ cpp::result ModelService::DeleteModel( CTL_WRN("Error: " + model_entry.error()); return cpp::fail(model_entry.error()); } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + auto yaml_fp = fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)); + yaml_handler.ModelConfigFromFile(yaml_fp.string()); auto mc = yaml_handler.GetModelConfig(); // Remove yaml file - std::filesystem::remove(model_entry.value().path_to_model_yaml); + std::filesystem::remove(yaml_fp); // Remove model files if they are not imported locally if (model_entry.value().branch_name != "imported") { if (mc.files.size() > 0) { if (mc.engine == "cortex.llamacpp") { for (auto& file : mc.files) { - std::filesystem::path gguf_p(file); + std::filesystem::path gguf_p( + fmu::ToAbsoluteCortexDataPath(fs::path(file))); std::filesystem::remove(gguf_p); } } else { - std::filesystem::path f(mc.files[0]); + std::filesystem::path f( + fmu::ToAbsoluteCortexDataPath(fs::path(mc.files[0]))); std::filesystem::remove_all(f); } } else { @@ -398,7 +414,8 @@ cpp::result ModelService::DeleteModel( cpp::result ModelService::StartModel( const std::string& host, int port, const std::string& model_handle) { - + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; @@ -408,7 +425,10 @@ cpp::result ModelService::StartModel( CTL_WRN("Error: " + model_entry.error()); return cpp::fail(model_entry.error()); } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto mc = yaml_handler.GetModelConfig(); httplib::Client cli(host + ":" + std::to_string(port)); @@ -416,7 +436,8 @@ cpp::result ModelService::StartModel( Json::Value json_data = mc.ToJson(); if (mc.files.size() > 0) { // TODO(sang) support multiple files - json_data["model_path"] = mc.files[0]; + json_data["model_path"] = + fmu::ToAbsoluteCortexDataPath(fs::path(mc.files[0])).string(); } else { LOG_WARN << "model_path is empty"; return false; @@ -453,6 +474,8 @@ cpp::result ModelService::StartModel( cpp::result ModelService::StopModel( const std::string& host, int port, const std::string& model_handle) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; @@ -462,7 +485,10 @@ cpp::result ModelService::StopModel( CTL_WRN("Error: " + model_entry.error()); return cpp::fail(model_entry.error()); } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto mc = yaml_handler.GetModelConfig(); httplib::Client cli(host + ":" + std::to_string(port)); @@ -497,6 +523,8 @@ cpp::result ModelService::StopModel( cpp::result ModelService::GetModelStatus( const std::string& host, int port, const std::string& model_handle) { + namespace fs = std::filesystem; + namespace fmu = file_manager_utils; cortex::db::Models modellist_handler; config::YamlHandler yaml_handler; @@ -506,7 +534,10 @@ cpp::result ModelService::GetModelStatus( CTL_WRN("Error: " + model_entry.error()); return cpp::fail(model_entry.error()); } - yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); + yaml_handler.ModelConfigFromFile( + fmu::ToAbsoluteCortexDataPath( + fs::path(model_entry.value().path_to_model_yaml)) + .string()); auto mc = yaml_handler.GetModelConfig(); httplib::Client cli(host + ":" + std::to_string(port)); diff --git a/engine/test/components/test_paths.cc b/engine/test/components/test_paths.cc new file mode 100644 index 000000000..b063b63cf --- /dev/null +++ b/engine/test/components/test_paths.cc @@ -0,0 +1,62 @@ +#include "gtest/gtest.h" +#include "utils/file_manager_utils.h" + +namespace file_manager_utils { +// Test suite +class PathTests : public ::testing::Test {}; + +// Test cases for GetAbsolutePath +TEST_F(PathTests, GetAbsolutePath_AbsolutePath_ReturnsSamePath) { + auto base = std::filesystem::path("/base"); + auto relative = std::filesystem::path("/absolute/path"); + EXPECT_EQ(GetAbsolutePath(base, relative), relative); +} + +TEST_F(PathTests, GetAbsolutePath_RelativePath_ReturnsCombinedPath) { + auto base = std::filesystem::path("/base"); + auto relative = std::filesystem::path("relative/path"); + EXPECT_EQ(GetAbsolutePath(base, relative), base / relative); +} + +// Test cases for IsSubpath +TEST_F(PathTests, IsSubpath_Subpath_ReturnsTrue) { + auto base = std::filesystem::path("/base"); + auto subpath = std::filesystem::path("/base/relative/path"); + EXPECT_TRUE(IsSubpath(base, subpath)); +} + +TEST_F(PathTests, IsSubpath_NotSubpath_ReturnsFalse) { + auto base = std::filesystem::path("/base"); + auto not_subpath = std::filesystem::path("/other/path"); + EXPECT_FALSE(IsSubpath(base, not_subpath)); +} + +TEST_F(PathTests, IsSubpath_EmptyRelative_ReturnsFalse) { + auto base = std::filesystem::path("/base"); + auto empty_path = std::filesystem::path(""); + EXPECT_FALSE(IsSubpath(base, empty_path)); +} + +TEST_F(PathTests, IsSubpath_SamePath_ReturnsTrue) { + auto base = std::filesystem::path("/base"); + EXPECT_TRUE(IsSubpath(base, base)); +} + +// Test cases for Subtract +TEST_F(PathTests, Subtract_SubtractingBaseFromSubPath_ReturnsRelativePath) { + auto base = std::filesystem::path("/base"); + auto subpath = std::filesystem::path("/base/relative/path"); + EXPECT_EQ(Subtract(subpath, base), std::filesystem::path("relative/path")); +} + +TEST_F(PathTests, Subtract_NotASubPath_ReturnsOriginalPath) { + auto base = std::filesystem::path("/base"); + auto not_subpath = std::filesystem::path("/other/path"); + EXPECT_EQ(Subtract(not_subpath, base), not_subpath); +} + +TEST_F(PathTests, Subtract_IdenticalPaths_ReturnsEmptyRelative) { + auto base = std::filesystem::path("/base"); + EXPECT_EQ(Subtract(base, base), std::filesystem::path(".")); +} +} // namespace file_manager_utils \ No newline at end of file diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index 6e8dacced..2d9801249 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -6,7 +6,6 @@ #include "services/download_service.h" #include "utils/config_yaml_utils.h" - #if defined(__APPLE__) && defined(__MACH__) #include #elif defined(__linux__) @@ -292,4 +291,40 @@ inline std::string DownloadTypeToString(DownloadType type) { } } +inline std::filesystem::path GetAbsolutePath(const std::filesystem::path& base, + const std::filesystem::path& r) { + if (r.is_absolute()) { + return r; + } else { + return base / r; + } +} + +inline bool IsSubpath(const std::filesystem::path& base, + const std::filesystem::path& path) { + if (base == path) + return true; + auto rel = std::filesystem::relative(path, base); + return !rel.empty() && rel.native()[0] != '.'; +} + +inline std::filesystem::path Subtract(const std::filesystem::path& path, + const std::filesystem::path& base) { + if (IsSubpath(base, path)) { + return path.lexically_relative(base); + } else { + return path; + } +} + +inline std::filesystem::path ToRelativeCortexDataPath( + const std::filesystem::path& path) { + return Subtract(path, GetCortexDataPath()); +} + +inline std::filesystem::path ToAbsoluteCortexDataPath( + const std::filesystem::path& path) { + return GetAbsolutePath(GetCortexDataPath(), path); +} + } // namespace file_manager_utils