Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: nightly updater #1175

Merged
merged 8 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ if(DEFINED CMAKE_JS_INC)
# define NPI_VERSION
add_compile_definitions(NAPI_VERSION=8)
endif()

add_compile_definitions(CORTEX_VARIANT="${CORTEX_VARIANT}")
add_compile_definitions(CORTEX_CPP_VERSION="${CORTEX_CPP_VERSION}")
add_compile_definitions(CORTEX_CONFIG_FILE_PATH="${CORTEX_CONFIG_FILE_PATH}")
Expand Down
139 changes: 80 additions & 59 deletions engine/commands/cortex_upd_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,35 @@
#include "utils/file_manager_utils.h"
#include "utils/logging_utils.h"
#include "utils/system_info_utils.h"
#include "server_stop_cmd.h"

namespace commands {

namespace {
const std::string kCortexBinary = "cortex-cpp";
}

CortexUpdCmd::CortexUpdCmd() {}

void CortexUpdCmd::Exec(std::string v) {
// TODO(sang) stop server if it is running
// {
// commands::ServerStopCmd ssc("127.0.0.1", 3928);
// ssc.Exec();
// }
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
if (!GetNightly(v))
return;
} else {
if (!GetProAndBeta(v))
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
return;
}
CLI_LOG("Update cortex sucessfully");
}

bool CortexUpdCmd::GetProAndBeta(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;
return false;
}
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);

Expand All @@ -50,7 +61,7 @@ void CortexUpdCmd::Exec(std::string v) {
std::string matched_variant = "";
for (auto& asset : assets) {
auto asset_name = asset["name"].get<std::string>();
if (asset_name.find("cortex-cpp") != std::string::npos &&
if (asset_name.find(kCortexBinary) != std::string::npos &&
asset_name.find(os_arch) != std::string::npos) {
matched_variant = asset_name;
break;
Expand All @@ -59,7 +70,7 @@ void CortexUpdCmd::Exec(std::string v) {
}
if (matched_variant.empty()) {
CTL_ERR("No variant found for " << os_arch);
return;
return false;
}
CTL_INF("Matched variant: " << matched_variant);

Expand Down Expand Up @@ -99,73 +110,83 @@ void CortexUpdCmd::Exec(std::string v) {
archive_utils::ExtractArchive(download_path.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!");
});
break;
}
}
} catch (const nlohmann::json::parse_error& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl;
return;
return false;
}
} else {
CTL_ERR("HTTP error: " << res->status);
return;
return false;
}
} else {
auto err = res.error();
CTL_ERR("HTTP error: " << httplib::to_string(err));
return;
return false;
}
#if defined(_WIN32)
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto temp = executable_path / "cortex_tmp.exe";
remove(temp.string().c_str()); // ignore return code

auto src =
executable_path / "cortex" / kCortexBinary / (kCortexBinary + ".exe");
auto dst = executable_path / (kCortexBinary + ".exe");
// Rename
rename(dst.string().c_str(), temp.string().c_str());
// Update
CopyFile(const_cast<char*>(src.string().c_str()),
const_cast<char*>(dst.string().c_str()), false);
auto download_folder = executable_path / "cortex";
remove(download_folder);
remove(temp.string().c_str());
#else

// Replace binay file
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto temp = executable_path / "cortex_tmp";
auto src = executable_path / "cortex" / kCortexBinary / kCortexBinary;
auto dst = executable_path / kCortexBinary;
if (std::rename(dst.string().c_str(), temp.string().c_str())) {
CTL_ERR("Failed to rename from " << dst.string() << " to "
<< temp.string());
return;
}
try {
std::filesystem::copy_file(
src, dst, std::filesystem::copy_options::overwrite_existing);
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
std::filesystem::perms::group_all |
std::filesystem::perms::others_read |
std::filesystem::perms::others_exec);
std::filesystem::remove(temp);
auto download_folder = executable_path / "cortex/";
std::filesystem::remove_all(download_folder);
} catch (const std::exception& e) {
CTL_WRN("Something wrong happened: " << e.what());
return;
auto src = executable_path / "cortex" / "cortex-cpp" / GetCortexBinary();
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
auto dst = executable_path / GetCortexBinary();
return ReplaceBinaryInflight(src, dst);
}

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;
}
#endif
CLI_LOG("Update cortex sucessfully");
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) {
// try to unzip the downloaded file
std::filesystem::path download_path{absolute_path};
CTL_INF("Downloaded engine path: " << download_path.string());

std::filesystem::path extract_path =
download_path.parent_path().parent_path();

archive_utils::ExtractArchive(download_path.string(),
extract_path.string());

CTL_INF("Finished!");
});

// Replace binay file
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto src = executable_path / "cortex" / GetCortexBinary();
auto dst = executable_path / GetCortexBinary();
return ReplaceBinaryInflight(src, dst);
}

} // namespace commands
116 changes: 114 additions & 2 deletions engine/commands/cortex_upd_cmd.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,125 @@
#pragma once
#include <string>
#include <optional>
#include <string>

#include "httplib.h"
#include "nlohmann/json.hpp"
#include "utils/file_manager_utils.h"
#include "utils/logging_utils.h"

namespace commands {
#ifndef CORTEX_VARIANT
#define CORTEX_VARIANT file_manager_utils::kProdVariant
#endif
constexpr const auto kNightlyHost = "https://delta.jan.ai";
constexpr const auto kNightlyFileName = "cortex-nightly.tar.gz";
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
const std::string kCortexBinary = "cortex";

inline std::string GetCortexBinary() {
#if defined(_WIN32)
constexpr const bool has_exe = true;
#else
constexpr const bool has_exe = false;
#endif
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
return has_exe ? kCortexBinary + "-nightly.exe"
: kCortexBinary + "-nightly";
} else if (CORTEX_VARIANT == file_manager_utils::kBetaVariant) {
return has_exe ? kCortexBinary + "-beta.exe" : kCortexBinary + "-beta";
} else {
return has_exe ? kCortexBinary + ".exe" : kCortexBinary;
}
}

inline std::string GetHostName() {
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
return "https://delta.jan.ai";
} else {
return "https://api.github.com";
}
}

inline std::string GetReleasePath() {
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
return "/cortex/latest/version.json";
} else {
return "/repos/janhq/cortex.cpp/releases/latest";
}
}

inline void CheckNewUpdate() {
auto host_name = GetHostName();
auto release_path = GetReleasePath();
CTL_INF("Engine release path: " << host_name << release_path);

class CortexUpdCmd{
httplib::Client cli(host_name);
if (auto res = cli.Get(release_path)) {
if (res->status == httplib::StatusCode::OK_200) {
try {
auto json_res = nlohmann::json::parse(res->body);
std::string latest_version = json_res["tag_name"].get<std::string>();
std::string current_version = CORTEX_CPP_VERSION;
if (current_version != latest_version) {
CLI_LOG("\nA new release of cortex is available: "
<< current_version << " -> " << latest_version);
CLI_LOG("To upgrade, run: cortex update");
// CLI_LOG(json_res["html_url"].get<std::string>());
}
} catch (const nlohmann::json::parse_error& e) {
CTL_INF("JSON parse error: " << e.what());
}
} else {
CTL_INF("HTTP error: " << res->status);
}
} else {
auto err = res.error();
CTL_INF("HTTP error: " << httplib::to_string(err));
}
}

inline bool ReplaceBinaryInflight(const std::filesystem::path& src,
const std::filesystem::path& dst) {
if (src == dst) {
// Already has the newest
return true;
}
std::filesystem::path temp = dst.parent_path() / "cortex_temp";
std::cout << temp.string() << std::endl;

try {
if (std::filesystem::exists(temp)) {
std::filesystem::remove(temp);
}

std::rename(dst.string().c_str(), temp.string().c_str());
std::filesystem::copy_file(
src, dst, std::filesystem::copy_options::overwrite_existing);
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
std::filesystem::perms::group_all |
std::filesystem::perms::others_read |
std::filesystem::perms::others_exec);
auto download_folder = src.parent_path();
std::filesystem::remove_all(download_folder);
} catch (const std::exception& e) {
CTL_ERR("Something wrong happened: " << e.what());
if (std::filesystem::exists(temp)) {
std::rename(temp.string().c_str(), dst.string().c_str());
CLI_LOG("Restored binary file");
}
return false;
}

return true;
}

class CortexUpdCmd {
public:
CortexUpdCmd();
void Exec(std::string version);

private:
bool GetProAndBeta(const std::string& v);
bool GetNightly(const std::string& v);
};

} // namespace commands
29 changes: 1 addition & 28 deletions engine/controllers/command_line_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,34 +193,7 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) {
// Check new update, only check for stable release for now
#ifdef CORTEX_CPP_VERSION
if (check_update) {
constexpr auto github_host = "https://api.github.com";
std::ostringstream release_path;
release_path << "/repos/janhq/cortex.cpp/releases/latest";
CTL_INF("Engine release path: " << github_host << release_path.str());

httplib::Client cli(github_host);
if (auto res = cli.Get(release_path.str())) {
if (res->status == httplib::StatusCode::OK_200) {
try {
auto json_res = nlohmann::json::parse(res->body);
std::string latest_version = json_res["tag_name"].get<std::string>();
std::string current_version = CORTEX_CPP_VERSION;
if (current_version != latest_version) {
CLI_LOG("\nA new release of cortex is available: "
<< current_version << " -> " << latest_version);
CLI_LOG("To upgrade, run: cortex update");
CLI_LOG(json_res["html_url"].get<std::string>());
}
} catch (const nlohmann::json::parse_error& e) {
CTL_INF("JSON parse error: " << e.what());
}
} else {
CTL_INF("HTTP error: " << res->status);
}
} else {
auto err = res.error();
CTL_INF("HTTP error: " << httplib::to_string(err));
}
commands::CheckNewUpdate();
}
#endif

Expand Down
1 change: 1 addition & 0 deletions engine/services/download_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ void DownloadService::StartDownloadItem(
}
if (current == total) {
outputFile.flush();
outputFile.close();
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved
CLI_LOG("Done download: " << static_cast<double>(total) / 1024 / 1024
<< " MiB");
if (callback.has_value()) {
Expand Down
Loading