From a6d95c61ce011eab9a417208d657ab64476a9f5b Mon Sep 17 00:00:00 2001 From: James Date: Mon, 9 Sep 2024 16:58:38 +0700 Subject: [PATCH 1/3] feat: using libcurl to download --- engine/CMakeLists.txt | 2 + engine/commands/cortex_upd_cmd.cc | 139 +++++++-------- engine/commands/cortex_upd_cmd.h | 7 +- engine/commands/engine_init_cmd.cc | 153 +++++++--------- engine/commands/engine_init_cmd.h | 3 - engine/commands/model_pull_cmd.cc | 5 +- engine/controllers/engines.cc | 71 ++++---- engine/e2e-test/test_cli_engine_install.py | 2 +- engine/exceptions/failed_curl_exception.h | 10 ++ .../exceptions/failed_init_curl_exception.h | 10 ++ .../exceptions/failed_open_file_exception.h | 10 ++ engine/exceptions/malformed_url_exception.h | 10 ++ engine/main.cc | 10 ++ engine/services/download_service.cc | 165 ++++++++++-------- engine/services/download_service.h | 80 +++++---- engine/utils/cortexso_parser.h | 38 ++-- engine/utils/file_manager_utils.h | 13 +- engine/utils/format_utils.h | 39 +++++ engine/utils/model_callback_utils.h | 103 ++++++----- engine/utils/url_parser.h | 84 +++++++++ engine/vcpkg.json | 1 + 21 files changed, 579 insertions(+), 376 deletions(-) create mode 100644 engine/exceptions/failed_curl_exception.h create mode 100644 engine/exceptions/failed_init_curl_exception.h create mode 100644 engine/exceptions/failed_open_file_exception.h create mode 100644 engine/exceptions/malformed_url_exception.h create mode 100644 engine/utils/format_utils.h create mode 100644 engine/utils/url_parser.h diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 58661c508..c4afdaff4 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -106,6 +106,7 @@ find_package(CLI11 CONFIG REQUIRED) find_package(unofficial-minizip CONFIG REQUIRED) find_package(LibArchive REQUIRED) find_package(tabulate CONFIG REQUIRED) +find_package(CURL REQUIRED) # Build using CMAKE-JS if(DEFINED CMAKE_JS_INC) @@ -150,6 +151,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE CLI11::CLI11) target_link_libraries(${PROJECT_NAME} PRIVATE unofficial::minizip::minizip) target_link_libraries(${PROJECT_NAME} PRIVATE LibArchive::LibArchive) target_link_libraries(${PROJECT_NAME} PRIVATE tabulate::tabulate) +target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl) # Build using CMAKE-JS if(DEFINED CMAKE_JS_INC) diff --git a/engine/commands/cortex_upd_cmd.cc b/engine/commands/cortex_upd_cmd.cc index ca25f63d3..7423168f1 100644 --- a/engine/commands/cortex_upd_cmd.cc +++ b/engine/commands/cortex_upd_cmd.cc @@ -1,6 +1,3 @@ -// clang-format off -#include "utils/cortex_utils.h" -// clang-format on #include "cortex_upd_cmd.h" #include "httplib.h" #include "nlohmann/json.hpp" @@ -10,11 +7,10 @@ #include "utils/file_manager_utils.h" #include "utils/logging_utils.h" #include "utils/system_info_utils.h" +#include "utils/url_parser.h" namespace commands { -CortexUpdCmd::CortexUpdCmd() {} - void CortexUpdCmd::Exec(std::string v) { { auto config = file_manager_utils::GetCortexConfig(); @@ -38,14 +34,7 @@ void CortexUpdCmd::Exec(std::string v) { } bool CortexUpdCmd::GetStableAndBeta(const std::string& v) { - // Check if the architecture and OS are supported auto system_info = system_info_utils::GetSystemInfo(); - if (system_info.arch == system_info_utils::kUnsupported || - system_info.os == system_info_utils::kUnsupported) { - CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", " - << system_info.arch); - return false; - } CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); // Download file @@ -84,38 +73,35 @@ bool CortexUpdCmd::GetStableAndBeta(const std::string& v) { for (auto& asset : assets) { auto asset_name = asset["name"].get(); if (asset_name == matched_variant) { - std::string host{"https://github.com"}; - - auto full_url = asset["browser_download_url"].get(); - std::string path = full_url.substr(host.length()); - - auto fileName = asset["name"].get(); - CTL_INF("URL: " << full_url); - - auto download_task = DownloadTask{.id = "cortex", - .type = DownloadType::Cortex, - .error = std::nullopt, - .items = {DownloadItem{ - .id = "cortex", - .host = host, - .fileName = fileName, - .type = DownloadType::Cortex, - .path = path, - }}}; - - DownloadService download_service; - download_service.AddDownloadTask( - download_task, - [this](const std::string& absolute_path, bool unused) { + auto download_url = + asset["browser_download_url"].get(); + auto file_name = asset["name"].get(); + CTL_INF("Download url: " << download_url); + + auto local_path = + file_manager_utils::GetExecutableFolderContainerPath() / + "cortex" / asset_name; + auto download_task{DownloadTask{.id = "cortex", + .type = DownloadType::Cortex, + .items = {DownloadItem{ + .id = "cortex", + .downloadUrl = download_url, + .localPath = local_path, + }}}}; + + DownloadService().AddDownloadTask( + download_task, [](const DownloadTask& finishedTask) { // try to unzip the downloaded file - std::filesystem::path download_path{absolute_path}; - CTL_INF("Downloaded engine path: " << download_path.string()); + CTL_INF("Downloaded engine path: " + << finishedTask.items[0].localPath.string()); - std::filesystem::path extract_path = - download_path.parent_path().parent_path(); + auto extract_path = finishedTask.items[0] + .localPath.parent_path() + .parent_path(); - archive_utils::ExtractArchive(download_path.string(), - extract_path.string()); + archive_utils::ExtractArchive( + finishedTask.items[0].localPath.string(), + extract_path.string()); CTL_INF("Finished!"); }); @@ -145,56 +131,59 @@ bool CortexUpdCmd::GetStableAndBeta(const std::string& v) { } bool CortexUpdCmd::GetNightly(const std::string& v) { - // Check if the architecture and OS are supported auto system_info = system_info_utils::GetSystemInfo(); - if (system_info.arch == system_info_utils::kUnsupported || - system_info.os == system_info_utils::kUnsupported) { - CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", " - << system_info.arch); - return false; - } CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); // Download file std::string version = v.empty() ? "latest" : std::move(v); - std::ostringstream release_path; - release_path << "cortex/" << version << "/" << system_info.os << "-" - << system_info.arch << "/" << kNightlyFileName; - CTL_INF("Engine release path: " << kNightlyHost << "/" << release_path.str()); - - auto download_task = DownloadTask{.id = "cortex", - .type = DownloadType::Cortex, - .error = std::nullopt, - .items = {DownloadItem{ - .id = "cortex", - .host = kNightlyHost, - .fileName = kNightlyFileName, - .type = DownloadType::Cortex, - .path = release_path.str(), - }}}; - - DownloadService download_service; - download_service.AddDownloadTask( - download_task, [this](const std::string& absolute_path, bool unused) { + std::string os_arch{system_info.os + "-" + system_info.arch}; + const char* paths[] = { + "cortex", + version.c_str(), + os_arch.c_str(), + kNightlyFileName, + }; + std::vector path_list(paths, std::end(paths)); + auto url_obj = url_parser::Url{ + .protocol = "https", + .host = kNightlyHost, + .pathParams = path_list, + }; + + CTL_INF("Engine release path: " << url_parser::FromUrl(url_obj)); + + std::filesystem::path localPath = + file_manager_utils::GetExecutableFolderContainerPath() / "cortex" / + path_list.back(); + auto download_task = + DownloadTask{.id = "cortex", + .type = DownloadType::Cortex, + .items = {DownloadItem{ + .id = "cortex", + .downloadUrl = url_parser::FromUrl(url_obj), + .localPath = localPath, + }}}; + + DownloadService().AddDownloadTask( + download_task, [](const DownloadTask& finishedTask) { // try to unzip the downloaded file - std::filesystem::path download_path{absolute_path}; - CTL_INF("Downloaded engine path: " << download_path.string()); + CTL_INF("Downloaded engine path: " + << finishedTask.items[0].localPath.string()); - std::filesystem::path extract_path = - download_path.parent_path().parent_path(); + auto extract_path = + finishedTask.items[0].localPath.parent_path().parent_path(); - archive_utils::ExtractArchive(download_path.string(), + archive_utils::ExtractArchive(finishedTask.items[0].localPath.string(), extract_path.string()); CTL_INF("Finished!"); }); - // Replace binay file + // Replace binary file auto executable_path = file_manager_utils::GetExecutableFolderContainerPath(); auto src = std::filesystem::temp_directory_path() / "cortex" / kCortexBinary / GetCortexBinary(); auto dst = executable_path / GetCortexBinary(); return ReplaceBinaryInflight(src, dst); } - -} // namespace commands \ No newline at end of file +} // namespace commands diff --git a/engine/commands/cortex_upd_cmd.h b/engine/commands/cortex_upd_cmd.h index b8aac9e7b..24aae5725 100644 --- a/engine/commands/cortex_upd_cmd.h +++ b/engine/commands/cortex_upd_cmd.h @@ -1,5 +1,4 @@ #pragma once -#include #include #include "httplib.h" @@ -11,7 +10,7 @@ namespace commands { #ifndef CORTEX_VARIANT #define CORTEX_VARIANT file_manager_utils::kProdVariant #endif -constexpr const auto kNightlyHost = "https://delta.jan.ai"; +constexpr const auto kNightlyHost = "delta.jan.ai"; constexpr const auto kNightlyFileName = "cortex-nightly.tar.gz"; const std::string kCortexBinary = "cortex"; @@ -113,12 +112,10 @@ inline bool ReplaceBinaryInflight(const std::filesystem::path& src, class CortexUpdCmd { public: - CortexUpdCmd(); void Exec(std::string version); private: bool GetStableAndBeta(const std::string& v); bool GetNightly(const std::string& v); }; - -} // namespace commands \ No newline at end of file +} // namespace commands diff --git a/engine/commands/engine_init_cmd.cc b/engine/commands/engine_init_cmd.cc index 743b39cdc..16a1f2783 100644 --- a/engine/commands/engine_init_cmd.cc +++ b/engine/commands/engine_init_cmd.cc @@ -18,28 +18,7 @@ EngineInitCmd::EngineInitCmd(std::string engineName, std::string version) : engineName_(std::move(engineName)), version_(std::move(version)) {} bool EngineInitCmd::Exec() const { - if (engineName_.empty()) { - CTL_ERR("Engine name is required"); - return false; - } - - // Check if the architecture and OS are supported auto system_info = system_info_utils::GetSystemInfo(); - if (system_info.arch == system_info_utils::kUnsupported || - system_info.os == system_info_utils::kUnsupported) { - CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", " - << system_info.arch); - return false; - } - CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); - - // check if engine is supported - if (std::find(supportedEngines_.begin(), supportedEngines_.end(), - engineName_) == supportedEngines_.end()) { - CTL_ERR("Engine not supported"); - return false; - } - constexpr auto gitHubHost = "https://api.github.com"; std::string version = version_.empty() ? "latest" : version_; std::ostringstream engineReleasePath; @@ -90,51 +69,57 @@ bool EngineInitCmd::Exec() const { for (auto& asset : assets) { auto assetName = asset["name"].get(); if (assetName == matched_variant) { - std::string host{"https://github.com"}; + auto download_url = + asset["browser_download_url"].get(); + auto file_name = asset["name"].get(); + CTL_INF("Download url: " << download_url); + + std::filesystem::path engine_folder_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString( + DownloadType::Engine)) / + engineName_; + + if (!std::filesystem::exists(engine_folder_path)) { + CTL_INF("Creating " << engine_folder_path.string()); + std::filesystem::create_directories(engine_folder_path); + } - auto full_url = asset["browser_download_url"].get(); - std::string path = full_url.substr(host.length()); + CTL_INF("Engine folder path: " << engine_folder_path.string() + << "\n"); + auto local_path = engine_folder_path / file_name; + auto downloadTask{DownloadTask{.id = engineName_, + .type = DownloadType::Engine, + .items = {DownloadItem{ + .id = engineName_, + .downloadUrl = download_url, + .localPath = local_path, + }}}}; - auto fileName = asset["name"].get(); - CTL_INF("URL: " << full_url); + DownloadService download_service; + download_service.AddDownloadTask( + downloadTask, [](const DownloadTask& finishedTask) { + // try to unzip the downloaded file + CTL_INF("Engine zip path: " + << finishedTask.items[0].localPath.string()); - auto downloadTask = DownloadTask{.id = engineName_, - .type = DownloadType::Engine, - .error = std::nullopt, - .items = {DownloadItem{ - .id = engineName_, - .host = host, - .fileName = fileName, - .type = DownloadType::Engine, - .path = path, - }}}; + std::filesystem::path extract_path = + finishedTask.items[0] + .localPath.parent_path() + .parent_path(); - DownloadService download_service; - download_service.AddDownloadTask(downloadTask, [this]( - const std::string& - absolute_path, - bool unused) { - // try to unzip the downloaded file - std::filesystem::path downloadedEnginePath{absolute_path}; - CTL_INF( - "Downloaded engine path: " << downloadedEnginePath.string()); - - std::filesystem::path extract_path = - downloadedEnginePath.parent_path().parent_path(); - - archive_utils::ExtractArchive(downloadedEnginePath.string(), - extract_path.string()); - - // remove the downloaded file - // TODO(any) Could not delete file on Windows because it is currently hold by httplib(?) - // Not sure about other platforms - try { - std::filesystem::remove(absolute_path); - } catch (const std::exception& e) { - CTL_WRN("Could not delete file: " << e.what()); - } - CTL_INF("Finished!"); - }); + archive_utils::ExtractArchive( + finishedTask.items[0].localPath.string(), + extract_path.string()); + + // remove the downloaded file + try { + std::filesystem::remove(finishedTask.items[0].localPath); + } catch (const std::exception& e) { + CTL_WRN("Could not delete file: " << e.what()); + } + CTL_INF("Finished!"); + }); if (system_info.os == "mac" || engineName_ == "cortex.onnx") { // mac and onnx engine does not require cuda toolkit return true; @@ -146,7 +131,7 @@ bool EngineInitCmd::Exec() const { const std::string download_id = "cuda"; // TODO: we don't have API to retrieve list of cuda toolkit dependencies atm because we hosting it at jan - // will have better logic after https://github.com/janhq/cortex/issues/1046 finished + // will have better logic after https://github.com/janhq/cortex/issues/1046 finished // for now, assume that we have only 11.7 and 12.4 auto suitable_toolkit_version = ""; if (engineName_ == "cortex.tensorrt-llm") { @@ -174,33 +159,26 @@ bool EngineInitCmd::Exec() const { return false; } - std::ostringstream cuda_toolkit_path; - cuda_toolkit_path << "dist/cuda-dependencies/" - << cuda_driver_version << "/" << system_info.os - << "/" << cuda_toolkit_file_name; + std::ostringstream cuda_toolkit_url; + cuda_toolkit_url << jan_host << "/" << "dist/cuda-dependencies/" + << cuda_driver_version << "/" << system_info.os + << "/" << cuda_toolkit_file_name; LOG_DEBUG << "Cuda toolkit download url: " << jan_host - << cuda_toolkit_path.str(); - - auto downloadCudaToolkitTask = DownloadTask{ + << cuda_toolkit_url.str(); + auto cuda_toollkit_local_path = + file_manager_utils::GetExecutableFolderContainerPath() / + cuda_toolkit_file_name; + auto downloadCudaToolkitTask{DownloadTask{ .id = download_id, .type = DownloadType::CudaToolkit, - .error = std::nullopt, - .items = {DownloadItem{ - .id = download_id, - .host = jan_host, - .fileName = cuda_toolkit_file_name, - .type = DownloadType::CudaToolkit, - .path = cuda_toolkit_path.str(), - }}, - }; + .items = {DownloadItem{.id = download_id, + .downloadUrl = cuda_toolkit_url.str(), + .localPath = cuda_toollkit_local_path}}, + }}; download_service.AddDownloadTask( - downloadCudaToolkitTask, - [this](const std::string& absolute_path, bool unused) { - LOG_DEBUG << "Downloaded cuda path: " << absolute_path; - // try to unzip the downloaded file - std::filesystem::path downloaded_path{absolute_path}; + downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { // TODO(any) This is a temporary fix. The issue will be fixed when we has CIs // to pack CUDA dependecies into engine release auto get_engine_path = [](std::string_view e) { @@ -213,14 +191,15 @@ bool EngineInitCmd::Exec() const { std::string engine_path = file_manager_utils::GetCortexDataPath().string() + get_engine_path(engineName_); - archive_utils::ExtractArchive(absolute_path, engine_path); + archive_utils::ExtractArchive(finishedTask.items[0].localPath, + engine_path); + try { - std::filesystem::remove(absolute_path); + std::filesystem::remove(finishedTask.items[0].localPath); } catch (std::exception& e) { CTL_ERR("Error removing downloaded file: " << e.what()); } }); - return true; } } diff --git a/engine/commands/engine_init_cmd.h b/engine/commands/engine_init_cmd.h index 8de74034e..c75d76f9d 100644 --- a/engine/commands/engine_init_cmd.h +++ b/engine/commands/engine_init_cmd.h @@ -14,8 +14,5 @@ class EngineInitCmd { private: std::string engineName_; std::string version_; - - static constexpr std::array supportedEngines_ = { - "cortex.llamacpp", "cortex.onnx", "cortex.tensorrt-llm"}; }; } // namespace commands \ No newline at end of file diff --git a/engine/commands/model_pull_cmd.cc b/engine/commands/model_pull_cmd.cc index f64ad0737..1ae637817 100644 --- a/engine/commands/model_pull_cmd.cc +++ b/engine/commands/model_pull_cmd.cc @@ -12,9 +12,8 @@ ModelPullCmd::ModelPullCmd(std::string model_handle, std::string branch) bool ModelPullCmd::Exec() { auto downloadTask = cortexso_parser::getDownloadTask(model_handle_, branch_); if (downloadTask.has_value()) { - DownloadService downloadService; - downloadService.AddDownloadTask(downloadTask.value(), - model_callback_utils::DownloadModelCb); + DownloadService().AddDownloadTask(downloadTask.value(), + model_callback_utils::DownloadModelCb); CTL_INF("Download finished"); return true; } else { diff --git a/engine/controllers/engines.cc b/engine/controllers/engines.cc index dd7ba3036..c9e2ba1b4 100644 --- a/engine/controllers/engines.cc +++ b/engine/controllers/engines.cc @@ -24,18 +24,6 @@ void Engines::InstallEngine( } auto system_info = system_info_utils::GetSystemInfo(); - if (system_info.arch == system_info_utils::kUnsupported || - system_info.os == system_info_utils::kUnsupported) { - Json::Value res; - res["message"] = "Unsupported OS or architecture"; - auto resp = cortex_utils::CreateCortexHttpJsonResponse(res); - resp->setStatusCode(k409Conflict); - callback(resp); - LOG_ERROR << "Unsupported OS or architecture: " << system_info.os << ", " - << system_info.arch; - return; - } - auto version{"latest"}; constexpr auto gitHubHost = "https://api.github.com"; @@ -54,41 +42,46 @@ void Engines::InstallEngine( for (auto& asset : assets) { auto assetName = asset["name"].get(); if (assetName.find(os_arch) != std::string::npos) { - std::string host{"https://github.com"}; - - auto full_url = asset["browser_download_url"].get(); - std::string path = full_url.substr(host.length()); - - auto fileName = asset["name"].get(); - LOG_INFO << "URL: " << full_url; - - auto downloadTask = DownloadTask{.id = engine, - .type = DownloadType::Engine, - .error = std::nullopt, - .items = {DownloadItem{ - .id = engine, - .host = host, - .fileName = fileName, - .type = DownloadType::Engine, - .path = path, - }}}; + auto download_url = + asset["browser_download_url"].get(); + auto name = asset["name"].get(); + LOG_INFO << "Download url: " << download_url; + + std::filesystem::path engine_folder_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString( + DownloadType::Engine)) / + engine; + + if (!std::filesystem::exists(engine_folder_path)) { + CTL_INF("Creating " << engine_folder_path.string()); + std::filesystem::create_directories(engine_folder_path); + } + auto local_path = engine_folder_path / assetName; + auto downloadTask{DownloadTask{.id = engine, + .type = DownloadType::Engine, + .items = {DownloadItem{ + .id = engine, + .downloadUrl = download_url, + .localPath = local_path, + }}}}; DownloadService().AddAsyncDownloadTask( - downloadTask, - [](const std::string& absolute_path, bool unused) { + downloadTask, [](const DownloadTask& finishedTask) { // try to unzip the downloaded file - std::filesystem::path downloadedEnginePath{absolute_path}; - LOG_INFO << "Downloaded engine path: " - << downloadedEnginePath.string(); - archive_utils::ExtractArchive( - downloadedEnginePath.string(), - downloadedEnginePath.parent_path() + finishedTask.items[0].localPath.string(), + finishedTask.items[0] + .localPath.parent_path() .parent_path() .string()); // remove the downloaded file - std::filesystem::remove(absolute_path); + try { + std::filesystem::remove(finishedTask.items[0].localPath); + } catch (const std::exception& e) { + LOG_WARN << "Could not delete file: " << e.what(); + } LOG_INFO << "Finished!"; }); diff --git a/engine/e2e-test/test_cli_engine_install.py b/engine/e2e-test/test_cli_engine_install.py index 494128b03..84af8b777 100644 --- a/engine/e2e-test/test_cli_engine_install.py +++ b/engine/e2e-test/test_cli_engine_install.py @@ -10,7 +10,7 @@ def test_engines_install_llamacpp_should_be_successfully(self): exit_code, output, error = run( "Install Engine", ["engines", "install", "cortex.llamacpp"] ) - assert "Download" in output, "Should display downloading message" + assert "Start downloading" in output, "Should display downloading message" assert exit_code == 0, f"Install engine failed with error: {error}" @pytest.mark.skipif(platform.system() != "Darwin", reason="macOS-specific test") diff --git a/engine/exceptions/failed_curl_exception.h b/engine/exceptions/failed_curl_exception.h new file mode 100644 index 000000000..095824334 --- /dev/null +++ b/engine/exceptions/failed_curl_exception.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class FailedCurlException : public std::runtime_error { + + public: + FailedCurlException(const std::string& message) + : std::runtime_error(message) {} +}; diff --git a/engine/exceptions/failed_init_curl_exception.h b/engine/exceptions/failed_init_curl_exception.h new file mode 100644 index 000000000..1843f9b80 --- /dev/null +++ b/engine/exceptions/failed_init_curl_exception.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class FailedInitCurlException : public std::runtime_error { + constexpr static auto kErrorMessage = "Failed to init CURL"; + + public: + FailedInitCurlException() : std::runtime_error(kErrorMessage) {} +}; diff --git a/engine/exceptions/failed_open_file_exception.h b/engine/exceptions/failed_open_file_exception.h new file mode 100644 index 000000000..8d014ead4 --- /dev/null +++ b/engine/exceptions/failed_open_file_exception.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class FailedOpenFileException : public std::runtime_error { + + public: + FailedOpenFileException(const std::string& message) + : std::runtime_error(message) {} +}; diff --git a/engine/exceptions/malformed_url_exception.h b/engine/exceptions/malformed_url_exception.h new file mode 100644 index 000000000..c4b6f4fab --- /dev/null +++ b/engine/exceptions/malformed_url_exception.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class MalformedUrlException : public std::runtime_error { + + public: + MalformedUrlException(const std::string& message) + : std::runtime_error(message) {} +}; diff --git a/engine/main.cc b/engine/main.cc index 272aa7bb8..81a026dd4 100644 --- a/engine/main.cc +++ b/engine/main.cc @@ -10,6 +10,7 @@ #include "utils/file_logger.h" #include "utils/file_manager_utils.h" #include "utils/logging_utils.h" +#include "utils/system_info_utils.h" #if defined(__APPLE__) && defined(__MACH__) #include // for dirname() @@ -151,6 +152,15 @@ void ForkProcess() { } int main(int argc, char* argv[]) { + // Stop the program if the system is not supported + auto system_info = system_info_utils::GetSystemInfo(); + if (system_info.arch == system_info_utils::kUnsupported || + system_info.os == system_info_utils::kUnsupported) { + CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", " + << system_info.arch); + return 1; + } + { file_manager_utils::CreateConfigFileIfNotExist(); } // Delete temporary file if it exists diff --git a/engine/services/download_service.cc b/engine/services/download_service.cc index 78a0c4757..08e37958c 100644 --- a/engine/services/download_service.cc +++ b/engine/services/download_service.cc @@ -1,96 +1,121 @@ +#include #include +#include #include #include -#include -#include #include #include "download_service.h" -#include "utils/file_manager_utils.h" +#include "exceptions/failed_curl_exception.h" +#include "exceptions/failed_init_curl_exception.h" +#include "exceptions/failed_open_file_exception.h" #include "utils/logging_utils.h" -void DownloadService::AddDownloadTask(const DownloadTask& task, - std::optional callback) { - tasks.push_back(task); +namespace { +size_t WriteCallback(void* ptr, size_t size, size_t nmemb, FILE* stream) { + size_t written = fwrite(ptr, size, nmemb, stream); + return written; +} +} // namespace + +void DownloadService::AddDownloadTask( + const DownloadTask& task, + std::optional callback) { + CLI_LOG("Validating download items, please wait.."); + // preprocess to check if all the item are valid + auto total_download_size{0}; + for (const auto& item : task.items) { + try { + total_download_size += GetFileSize(item.downloadUrl); + } catch (const FailedCurlException& e) { + CTL_ERR("Found invalid download item: " << item.downloadUrl << " - " + << e.what()); + throw; + } + } + // all items are valid, start downloading for (const auto& item : task.items) { - StartDownloadItem(task.id, item, callback); + CLI_LOG("Start downloading: " + item.localPath.filename().string()); + Download(task.id, item); + } + + if (callback.has_value()) { + callback.value()(task); } } +uint64_t DownloadService::GetFileSize(const std::string& url) const { + CURL* curl; + curl = curl_easy_init(); + + if (!curl) { + throw FailedInitCurlException(); + } + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + // if we have a failed here. it meant the url is invalid + throw FailedCurlException("CURL failed: " + + std::string(curl_easy_strerror(res))); + } + + curl_off_t content_length = 0; + res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, + &content_length); + return content_length; +} + void DownloadService::AddAsyncDownloadTask( - const DownloadTask& task, std::optional callback) { - tasks.push_back(task); + const DownloadTask& task, + std::optional callback) { for (const auto& item : task.items) { - // TODO: maybe apply std::async is better? std::thread([this, task, &callback, item]() { - this->StartDownloadItem(task.id, item, callback); + this->Download(task.id, item); }).detach(); } + + // TODO: how to call the callback when all the download has finished? } -void DownloadService::StartDownloadItem( - const std::string& downloadId, const DownloadItem& item, - std::optional callback) { - CTL_INF("Downloading item: " << downloadId); +void DownloadService::Download(const std::string& download_id, + const DownloadItem& download_item) { + CTL_INF("Absolute file output: " << download_item.localPath.string()); + + CURL* curl; + FILE* file; + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + throw FailedInitCurlException(); + } + + file = fopen(download_item.localPath.string().c_str(), "wb"); + if (!file) { + auto err_msg{"Failed to open output file " + + download_item.localPath.string()}; + throw FailedOpenFileException(err_msg); + } + + curl_easy_setopt(curl, CURLOPT_URL, download_item.downloadUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - auto containerFolderPath{file_manager_utils::GetContainerFolderPath( - file_manager_utils::downloadTypeToString(item.type))}; - CTL_INF("Container folder path: " << containerFolderPath.string() << "\n"); + res = curl_easy_perform(curl); - auto itemFolderPath{containerFolderPath / std::filesystem::path(downloadId)}; - CTL_INF("itemFolderPath: " << itemFolderPath.string()); - if (!std::filesystem::exists(itemFolderPath)) { - CTL_INF("Creating " << itemFolderPath.string()); - std::filesystem::create_directory(itemFolderPath); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); } - auto outputFilePath{itemFolderPath / std::filesystem::path(item.fileName)}; - CTL_INF("Absolute file output: " << outputFilePath.string()); - - uint64_t last = 0; - uint64_t tot = 0; - std::ofstream outputFile(outputFilePath, std::ios::binary); - - auto downloadUrl{item.host + "/" + item.path}; - CLI_LOG("Downloading url: " << downloadUrl); - - httplib::Client client(item.host); - - client.set_follow_location(true); - client.Get( - downloadUrl, - [](const httplib::Response& res) { - if (res.status != httplib::StatusCode::OK_200) { - LOG_ERROR << "HTTP error: " << res.reason; - return false; - } - return true; - }, - [&](const char* data, size_t data_length) { - tot += data_length; - outputFile.write(data, data_length); - return true; - }, - [&item, &last, &outputFile, &callback, outputFilePath, this]( - uint64_t current, uint64_t total) { - if (current - last > kUpdateProgressThreshold) { - last = current; - CLI_LOG("Downloading: " << current << " / " << total); - } - if (current == total) { - outputFile.flush(); - outputFile.close(); - CLI_LOG("Done download: " << static_cast(total) / 1024 / 1024 - << " MiB"); - if (callback.has_value()) { - auto need_parse_gguf = - item.path.find("cortexso") == std::string::npos; - callback.value()(outputFilePath.string(), need_parse_gguf); - } - return false; - } - return true; - }); + fclose(file); + curl_easy_cleanup(curl); } diff --git a/engine/services/download_service.h b/engine/services/download_service.h index b3c405c9a..2583f629f 100644 --- a/engine/services/download_service.h +++ b/engine/services/download_service.h @@ -1,66 +1,74 @@ #pragma once +#include #include #include +#include #include enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit, Cortex }; -enum class DownloadStatus { - Pending, - Downloading, - Error, - Downloaded, -}; - struct DownloadItem { std::string id; - std::string host; - - std::string fileName; - - DownloadType type; + std::string downloadUrl; - std::string path; + /** + * An absolute path to where the file is located (locally). + */ + std::filesystem::path localPath; std::optional checksum; + + std::string ToString() const { + std::ostringstream output; + output << "DownloadItem{id: " << id << ", downloadUrl: " << downloadUrl + << ", localContainerPath: " << localPath + << ", checksum: " << checksum.value_or("N/A") << "}"; + return output.str(); + } }; struct DownloadTask { std::string id; + DownloadType type; - std::optional error; + std::vector items; + + std::string ToString() const { + std::ostringstream output; + output << "DownloadTask{id: " << id << ", type: " << static_cast(type) + << ", items: ["; + for (const auto& item : items) { + output << item.ToString() << ", "; + } + output << "]}"; + return output.str(); + } }; class DownloadService { public: - /** - * @brief Synchronously download. - * - * @param task - */ - using DownloadItemCb = std::function; - void AddDownloadTask(const DownloadTask& task, - std::optional callback = std::nullopt); + using OnDownloadTaskSuccessfully = + std::function; + + void AddDownloadTask( + const DownloadTask& task, + std::optional callback = std::nullopt); void AddAsyncDownloadTask( const DownloadTask& task, - std::optional callback = std::nullopt); + std::optional callback = std::nullopt); - // TODO: [NamH] implement the following methods - // void removeTask(const std::string &id); - // void registerCallback - // setup folder path at runtime - // register action after downloaded + /** + * Getting file size for a provided url. + * + * @param url - url to get file size + */ + uint64_t GetFileSize(const std::string& url) const; private: - void StartDownloadItem(const std::string& downloadId, - const DownloadItem& item, - std::optional callback = std::nullopt); - - // store tasks so we can abort it later - std::vector tasks; - const int kUpdateProgressThreshold = 100000000; -}; \ No newline at end of file + void Download(const std::string& download_id, + const DownloadItem& download_item); +}; diff --git a/engine/utils/cortexso_parser.h b/engine/utils/cortexso_parser.h index 91efa1fff..a9592d98d 100644 --- a/engine/utils/cortexso_parser.h +++ b/engine/utils/cortexso_parser.h @@ -6,6 +6,7 @@ #include #include #include "httplib.h" +#include "utils/file_manager_utils.h" namespace cortexso_parser { constexpr static auto kHuggingFaceHost = "https://huggingface.co"; @@ -28,27 +29,32 @@ inline std::optional getDownloadTask( auto jsonResponse = json::parse(res->body); std::vector downloadItems{}; + std::filesystem::path model_container_path = + file_manager_utils::GetModelsContainerPath() / modelId; + file_manager_utils::CreateDirectoryRecursively( + model_container_path.string()); + for (auto& [key, value] : jsonResponse.items()) { std::ostringstream downloadUrlOutput; auto path = value["path"].get(); - downloadUrlOutput << repoAndModelIdStr << "/resolve/" << branch << "/" - << path; - const std::string downloadUrl = downloadUrlOutput.str(); + if (path == ".gitattributes" || path == ".gitignore" || + path == "README.md") { + continue; + } + downloadUrlOutput << kHuggingFaceHost << "/" << repoAndModelIdStr + << "/resolve/" << branch << "/" << path; + const std::string download_url = downloadUrlOutput.str(); + auto local_path = model_container_path / path; - DownloadItem downloadItem{}; - downloadItem.id = path; - downloadItem.host = kHuggingFaceHost; - downloadItem.fileName = path; - downloadItem.type = DownloadType::Model; - downloadItem.path = downloadUrl; - downloadItems.push_back(downloadItem); + downloadItems.push_back(DownloadItem{.id = path, + .downloadUrl = download_url, + .localPath = local_path}); } - DownloadTask downloadTask{}; - downloadTask.id = branch == "main" ? modelId : modelId + "-" + branch; - downloadTask.type = DownloadType::Model; - downloadTask.error = std::nullopt; - downloadTask.items = downloadItems; + DownloadTask downloadTask{ + .id = branch == "main" ? modelId : modelId + "-" + branch, + .type = DownloadType::Model, + .items = downloadItems}; return downloadTask; } catch (const json::parse_error& e) { @@ -61,4 +67,4 @@ inline std::optional getDownloadTask( } return std::nullopt; } -} // namespace cortexso_parser \ No newline at end of file +} // namespace cortexso_parser diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index 40d0df37f..e9ab6d515 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -110,7 +110,7 @@ inline std::filesystem::path GetConfigurationPath() { } inline void CreateConfigFileIfNotExist() { - auto config_path = file_manager_utils::GetConfigurationPath(); + auto config_path = GetConfigurationPath(); if (std::filesystem::exists(config_path)) { // already exists return; @@ -192,6 +192,15 @@ inline std::filesystem::path GetCortexLogPath() { return log_folder_path; } +inline void CreateDirectoryRecursively(const std::string& path) { + // Create the directories if they don't exist + if (std::filesystem::create_directories(path)) { + CTL_INF(path + " successfully created!"); + } else { + CTL_INF(path + " already exist!"); + } +} + inline std::filesystem::path GetModelsContainerPath() { auto cortex_path = GetCortexDataPath(); auto models_container_path = cortex_path / "models"; @@ -243,7 +252,7 @@ inline std::filesystem::path GetContainerFolderPath( return container_folder_path; } -inline std::string downloadTypeToString(DownloadType type) { +inline std::string DownloadTypeToString(DownloadType type) { switch (type) { case DownloadType::Model: return "Model"; diff --git a/engine/utils/format_utils.h b/engine/utils/format_utils.h new file mode 100644 index 000000000..7824d138c --- /dev/null +++ b/engine/utils/format_utils.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace format_utils { +inline std::string BytesToHumanReadable(uint64_t bytes) { + const uint64_t KB = 1024; + const uint64_t MB = KB * 1024; + const uint64_t GB = MB * 1024; + const uint64_t TB = GB * 1024; + + double result; + std::string unit; + + if (bytes >= TB) { + result = static_cast(bytes) / TB; + unit = "TB"; + } else if (bytes >= GB) { + result = static_cast(bytes) / GB; + unit = "GB"; + } else if (bytes >= MB) { + result = static_cast(bytes) / MB; + unit = "MB"; + } else if (bytes >= KB) { + result = static_cast(bytes) / KB; + unit = "KB"; + } else { + result = static_cast(bytes); + unit = "B"; + } + + std::ostringstream out; + // take 2 decimal points + out << std::fixed << std::setprecision(2) << result << " " << unit; + return out.str(); +} +} // namespace format_utils diff --git a/engine/utils/model_callback_utils.h b/engine/utils/model_callback_utils.h index 9f54d20d4..3a3b0f288 100644 --- a/engine/utils/model_callback_utils.h +++ b/engine/utils/model_callback_utils.h @@ -1,53 +1,78 @@ #pragma once + #include #include -#include -#include -#include #include "config/gguf_parser.h" #include "config/yaml_config.h" -#include "utils/file_manager_utils.h" +#include "services/download_service.h" #include "utils/logging_utils.h" namespace model_callback_utils { -inline void DownloadModelCb(const std::string& path, bool need_parse_gguf) { - - std::filesystem::path path_obj(path); - std::string filename(path_obj.filename().string()); - //TODO: handle many cases of downloaded items from other sources except cortexso. - if (filename.compare("model.yml") == 0) { - config::YamlHandler handler; - handler.ModelConfigFromFile(path); - config::ModelConfig model_config = handler.GetModelConfig(); - model_config.id = path_obj.parent_path().filename().string(); - - CTL_INF("Updating model config in " << path); - handler.UpdateModelConfig(model_config); - handler.WriteYamlFile(path_obj.parent_path().parent_path().string() + "/" + - model_config.id + ".yaml"); +inline void WriteYamlOutput(const DownloadItem& modelYmlDownloadItem) { + config::YamlHandler handler; + handler.ModelConfigFromFile(modelYmlDownloadItem.localPath.string()); + config::ModelConfig model_config = handler.GetModelConfig(); + model_config.id = + modelYmlDownloadItem.localPath.parent_path().filename().string(); + + CTL_INF("Updating model config in " + << modelYmlDownloadItem.localPath.string()); + handler.UpdateModelConfig(model_config); + std::string yaml_filename{model_config.id + ".yaml"}; + std::filesystem::path yaml_output = + modelYmlDownloadItem.localPath.parent_path().parent_path() / + yaml_filename; + handler.WriteYamlFile(yaml_output.string()); +} + +inline void ParseGguf(const DownloadItem& ggufDownloadItem) { + 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()}; + yaml_handler.UpdateModelConfig(model_config); + + std::string yaml_filename{model_config.id + ".yaml"}; + std::filesystem::path yaml_output = + ggufDownloadItem.localPath.parent_path().parent_path() / yaml_filename; + std::filesystem::path yaml_path(ggufDownloadItem.localPath.parent_path() / + "model.yml"); + if (!std::filesystem::exists(yaml_output)) { // if model.yml doesn't exist + yaml_handler.WriteYamlFile(yaml_output.string()); + } + if (!std::filesystem::exists(yaml_path)) { + yaml_handler.WriteYamlFile(yaml_path.string()); } - // currently, only handle downloaded model with only 1 .gguf file - // TODO: handle multipart gguf file or different model in 1 repo. - else if (path_obj.extension().string().compare(".gguf") == 0) { - if(!need_parse_gguf) return; - config::GGUFHandler gguf_handler; - config::YamlHandler yaml_handler; - gguf_handler.Parse(path); - config::ModelConfig model_config = gguf_handler.GetModelConfig(); - model_config.id = path_obj.parent_path().filename().string(); - model_config.files = {path}; - yaml_handler.UpdateModelConfig(model_config); - std::string yml_path(path_obj.parent_path().parent_path().string() + "/" + - model_config.id + ".yaml"); - std::string yaml_path(path_obj.parent_path().string() + "/model.yml"); - if (!std::filesystem::exists(yml_path)) { // if model.yml doesn't exist - yaml_handler.WriteYamlFile(yml_path); +} + +inline void DownloadModelCb(const DownloadTask& finishedTask) { + const DownloadItem* model_yml_di = nullptr; + const DownloadItem* gguf_di = nullptr; + auto need_parse_gguf = true; + + for (const auto& item : finishedTask.items) { + if (item.localPath.filename().string() == "model.yml") { + model_yml_di = &item; } - if (!std::filesystem::exists( - yaml_path)) { // if .yaml doesn't exist - yaml_handler.WriteYamlFile(yaml_path); + if (item.localPath.extension().string() == ".gguf") { + gguf_di = &item; } + if (item.downloadUrl.find("cortexso") != std::string::npos) { + // if downloading from cortexso, we dont need to parse gguf + need_parse_gguf = false; + } + } + + if (model_yml_di != nullptr) { + WriteYamlOutput(*model_yml_di); + } + + if (need_parse_gguf && gguf_di != nullptr) { + ParseGguf(*gguf_di); } } -} // namespace model_callback_utils \ No newline at end of file +} // namespace model_callback_utils diff --git a/engine/utils/url_parser.h b/engine/utils/url_parser.h new file mode 100644 index 000000000..b879fc95a --- /dev/null +++ b/engine/utils/url_parser.h @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include "exceptions/malformed_url_exception.h" + +namespace url_parser { +// TODO: add an unordered map to store the query +// TODO: add a function to construct a string from Url + +struct Url { + std::string protocol; + std::string host; + std::vector pathParams; + std::unordered_map> queries; +}; + +const std::regex url_regex( + R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)", + std::regex::extended); + +inline void SplitPathParams(const std::string& input, + std::vector& pathList) { + // split the path by '/' + std::string token; + std::istringstream tokenStream(input); + while (std::getline(tokenStream, token, '/')) { + pathList.push_back(token); + } +} + +inline Url FromUrlString(const std::string& urlString) { + Url url = { + .protocol = "", + .host = "", + .pathParams = {}, + }; + unsigned counter = 0; + + std::smatch url_match_result; + + auto protocolIndex{2}; + auto hostAndPortIndex{4}; + auto pathIndex{5}; + auto queryIndex{7}; + + if (std::regex_match(urlString, url_match_result, url_regex)) { + for (const auto& res : url_match_result) { + if (counter == protocolIndex) { + url.protocol = res; + } else if (counter == hostAndPortIndex) { + url.host = res; // TODO: split the port for completeness + } else if (counter == pathIndex) { + SplitPathParams(res, url.pathParams); + } else if (counter == queryIndex) { + // TODO: implement + } + counter++; + } + } else { + auto message{"Malformed URL: " + urlString}; + throw MalformedUrlException(message); + } + return url; +} + +inline std::string FromUrl(const Url& url) { + if (url.protocol.empty() || url.host.empty()) { + auto message{"Url must have protocol and host"}; + throw MalformedUrlException(message); + } + std::ostringstream url_string; + url_string << url.protocol << "://" << url.host; + + for (const auto& path : url.pathParams) { + url_string << "/" << path; + } + + // TODO: handle queries + + return url_string.str(); +} +} // namespace url_parser diff --git a/engine/vcpkg.json b/engine/vcpkg.json index fe4783ec8..8f7729524 100644 --- a/engine/vcpkg.json +++ b/engine/vcpkg.json @@ -1,5 +1,6 @@ { "dependencies": [ + "curl", "gtest", "cli11", { From e59f86a5640108ef8b3927f7b54ec50bf11cb702 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 13 Sep 2024 12:29:26 +0700 Subject: [PATCH 2/3] add unit test --- engine/commands/engine_init_cmd.cc | 4 ++-- engine/test/components/test_url_parser.cc | 26 +++++++++++++++++++++++ engine/utils/url_parser.h | 4 ++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 engine/test/components/test_url_parser.cc diff --git a/engine/commands/engine_init_cmd.cc b/engine/commands/engine_init_cmd.cc index 16a1f2783..b0c633042 100644 --- a/engine/commands/engine_init_cmd.cc +++ b/engine/commands/engine_init_cmd.cc @@ -191,8 +191,8 @@ bool EngineInitCmd::Exec() const { std::string engine_path = file_manager_utils::GetCortexDataPath().string() + get_engine_path(engineName_); - archive_utils::ExtractArchive(finishedTask.items[0].localPath, - engine_path); + archive_utils::ExtractArchive( + finishedTask.items[0].localPath.string(), engine_path); try { std::filesystem::remove(finishedTask.items[0].localPath); diff --git a/engine/test/components/test_url_parser.cc b/engine/test/components/test_url_parser.cc new file mode 100644 index 000000000..375ce385d --- /dev/null +++ b/engine/test/components/test_url_parser.cc @@ -0,0 +1,26 @@ +#include "gtest/gtest.h" +#include "utils/url_parser.h" + +class UrlParserTestSuite : public ::testing::Test { + protected: + constexpr static auto kValidUrlWithOnlyPaths{"https://jan.ai/path1/path2"}; +}; + +TEST_F(UrlParserTestSuite, TestParseUrlCorrectly) { + auto url = url_parser::FromUrlString(kValidUrlWithOnlyPaths); + + EXPECT_EQ(url.host, "jan.ai"); + EXPECT_EQ(url.protocol, "https"); + EXPECT_EQ(url.pathParams.size(), 2); +} + +TEST_F(UrlParserTestSuite, ConstructUrlCorrectly) { + auto url = url_parser::Url{ + .protocol = "https", + .host = "jan.ai", + .pathParams = {"path1", "path2"}, + }; + auto url_str = url_parser::FromUrl(url); + + EXPECT_EQ(url_str, kValidUrlWithOnlyPaths); +} diff --git a/engine/utils/url_parser.h b/engine/utils/url_parser.h index b879fc95a..dba3ec0a5 100644 --- a/engine/utils/url_parser.h +++ b/engine/utils/url_parser.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "exceptions/malformed_url_exception.h" @@ -26,6 +27,9 @@ inline void SplitPathParams(const std::string& input, std::string token; std::istringstream tokenStream(input); while (std::getline(tokenStream, token, '/')) { + if (token.empty()) { + continue; + } pathList.push_back(token); } } From c3c39469af42ffeb3a0ff9ca22710dc8ed9adab8 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 13 Sep 2024 15:08:34 +0700 Subject: [PATCH 3/3] chore: update download path for cuda-toolkit-deps --- engine/commands/engine_init_cmd.cc | 35 +++++++++++++++--------------- engine/utils/file_manager_utils.h | 10 ++++----- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/engine/commands/engine_init_cmd.cc b/engine/commands/engine_init_cmd.cc index b0c633042..350f3c6b1 100644 --- a/engine/commands/engine_init_cmd.cc +++ b/engine/commands/engine_init_cmd.cc @@ -7,7 +7,6 @@ #include "utils/archive_utils.h" #include "utils/system_info_utils.h" // clang-format on -#include "utils/cortex_utils.h" #include "utils/cuda_toolkit_utils.h" #include "utils/engine_matcher_utils.h" #include "utils/file_manager_utils.h" @@ -125,6 +124,11 @@ bool EngineInitCmd::Exec() const { return true; } + if (cuda_driver_version.empty()) { + CTL_WRN("No cuda driver, continue with CPU"); + return true; + } + // download cuda toolkit const std::string jan_host = "https://catalog.jan.ai"; const std::string cuda_toolkit_file_name = "cuda.tar.gz"; @@ -164,35 +168,30 @@ bool EngineInitCmd::Exec() const { << cuda_driver_version << "/" << system_info.os << "/" << cuda_toolkit_file_name; - LOG_DEBUG << "Cuda toolkit download url: " << jan_host + LOG_DEBUG << "Cuda toolkit download url: " << cuda_toolkit_url.str(); - auto cuda_toollkit_local_path = - file_manager_utils::GetExecutableFolderContainerPath() / + auto cuda_toolkit_local_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString( + DownloadType::CudaToolkit)) / cuda_toolkit_file_name; + LOG_DEBUG << "Download to: " << cuda_toolkit_local_path.string(); auto downloadCudaToolkitTask{DownloadTask{ .id = download_id, .type = DownloadType::CudaToolkit, .items = {DownloadItem{.id = download_id, .downloadUrl = cuda_toolkit_url.str(), - .localPath = cuda_toollkit_local_path}}, + .localPath = cuda_toolkit_local_path}}, }}; download_service.AddDownloadTask( downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { - // TODO(any) This is a temporary fix. The issue will be fixed when we has CIs - // to pack CUDA dependecies into engine release - auto get_engine_path = [](std::string_view e) { - if (e == "cortex.llamacpp") { - return cortex_utils::kLlamaLibPath; - } else { - return cortex_utils::kTensorrtLlmPath; - } - }; - std::string engine_path = - file_manager_utils::GetCortexDataPath().string() + - get_engine_path(engineName_); + auto engine_path = + file_manager_utils::GetEnginesContainerPath() / + engineName_; archive_utils::ExtractArchive( - finishedTask.items[0].localPath.string(), engine_path); + finishedTask.items[0].localPath.string(), + engine_path.string()); try { std::filesystem::remove(finishedTask.items[0].localPath); diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index e9ab6d515..8e449b3d2 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -229,24 +229,24 @@ inline std::filesystem::path GetEnginesContainerPath() { inline std::filesystem::path GetContainerFolderPath( const std::string_view type) { - const auto current_path{GetExecutableFolderContainerPath()}; - auto container_folder_path = std::filesystem::path{}; + std::filesystem::path container_folder_path; if (type == "Model") { container_folder_path = GetModelsContainerPath(); } else if (type == "Engine") { container_folder_path = GetEnginesContainerPath(); } else if (type == "CudaToolkit") { - container_folder_path = current_path; + container_folder_path = + std::filesystem::temp_directory_path() / "cuda-dependencies"; } else if (type == "Cortex") { container_folder_path = std::filesystem::temp_directory_path() / "cortex"; } else { - container_folder_path = current_path / "misc"; + container_folder_path = std::filesystem::temp_directory_path() / "misc"; } if (!std::filesystem::exists(container_folder_path)) { CTL_INF("Creating folder: " << container_folder_path.string() << "\n"); - std::filesystem::create_directory(container_folder_path); + std::filesystem::create_directories(container_folder_path); } return container_folder_path;