diff --git a/src/lib-cpp/include/Velopack.h b/src/lib-cpp/include/Velopack.h index 8032e303..a369dc9b 100644 --- a/src/lib-cpp/include/Velopack.h +++ b/src/lib-cpp/include/Velopack.h @@ -53,17 +53,13 @@ typedef void (*vpkc_progress_callback_t)(size_t progress); typedef void (*vpkc_log_callback_t)(const char* pszLevel, const char* pszMessage); typedef void (*vpkc_hook_callback_t)(const char* pszAppVersion); -typedef struct { - bool AllowVersionDowngrade; - char* ExplicitChannel; -} vpkc_options_t; - typedef enum { UPDATE_AVAILABLE = 0, NO_UPDATE_AVAILABLE = 1, ERROR = 2, } vpkc_update_check_t; +// !! AUTO-GENERATED-START C_TYPES typedef struct { char* RootAppDir; char* UpdateExePath; @@ -71,7 +67,7 @@ typedef struct { char* ManifestPath; char* CurrentBinaryDir; bool IsPortable; -} vpkc_locator_t; +} vpkc_locator_config_t; typedef struct { char* PackageId; @@ -90,8 +86,14 @@ typedef struct { bool IsDowngrade; } vpkc_update_info_t; +typedef struct { + bool AllowVersionDowngrade; + char* ExplicitChannel; +} vpkc_update_options_t; +// !! AUTO-GENERATED-END C_TYPES + // Update Manager -VPKC_EXPORT bool VPKC_CALL vpkc_new_update_manager(const char* pszUrlOrString, const vpkc_options_t* pOptions, vpkc_locator_t* pLocator, vpkc_update_manager_t* pManager); +VPKC_EXPORT bool VPKC_CALL vpkc_new_update_manager(const char* pszUrlOrString, vpkc_update_options_t* pOptions, vpkc_locator_config_t* pLocator, vpkc_update_manager_t* pManager); VPKC_EXPORT size_t VPKC_CALL vpkc_get_current_version(vpkc_update_manager_t* pManager, char* pszVersion, size_t cVersion); VPKC_EXPORT size_t VPKC_CALL vpkc_get_app_id(vpkc_update_manager_t* pManager, char* pszId, size_t cId); VPKC_EXPORT bool VPKC_CALL vpkc_is_portable(vpkc_update_manager_t* pManager); @@ -103,7 +105,7 @@ VPKC_EXPORT bool VPKC_CALL vpkc_wait_exit_then_apply_update(vpkc_update_manager_ // VelopackApp VPKC_EXPORT void VPKC_CALL vpkc_app_set_auto_apply_on_startup(bool bAutoApply); VPKC_EXPORT void VPKC_CALL vpkc_app_set_args(char** pArgs, size_t cArgs); -VPKC_EXPORT void VPKC_CALL vpkc_app_set_locator(vpkc_locator_t* pLocator); +VPKC_EXPORT void VPKC_CALL vpkc_app_set_locator(vpkc_locator_config_t* pLocator); VPKC_EXPORT void VPKC_CALL vpkc_app_set_hook_after_install(vpkc_hook_callback_t cbAfterInstall); VPKC_EXPORT void VPKC_CALL vpkc_app_set_hook_before_uninstall(vpkc_hook_callback_t cbBeforeUninstall); VPKC_EXPORT void VPKC_CALL vpkc_app_set_hook_before_update(vpkc_hook_callback_t cbBeforeUpdate); @@ -126,259 +128,302 @@ VPKC_EXPORT void VPKC_CALL vpkc_free_asset(vpkc_asset_t* pAsset); #ifdef __cplusplus namespace Velopack { - struct VelopackAsset { - std::string PackageId; - std::string Version; - std::string Type; - std::string FileName; - std::string SHA1; - std::string SHA256; - uint64_t Size; - std::string NotesMarkdown; - std::string NotesHtml; - }; - - struct UpdateInfo { - VelopackAsset TargetFullRelease; - bool IsDowngrade; - }; - - struct UpdateOptions { - bool AllowVersionDowngrade; - std::string ExplicitChannel; - }; - - struct VelopackLocator { - std::string RootAppDir; - std::string UpdateExePath; - std::string PackagesDir; - std::string ManifestPath; - std::string CurrentBinaryDir; - bool IsPortable; - }; - - static inline void throw_last_error() { - size_t neededSize = vpkc_get_last_error(nullptr, 0); - std::string strError(neededSize, '\0'); - vpkc_get_last_error(&strError[0], neededSize); - throw std::runtime_error(strError); - } - - static inline vpkc_locator_t to_vpkc(const VelopackLocator& locator) { - return { - const_cast(locator.RootAppDir.c_str()), - const_cast(locator.UpdateExePath.c_str()), - const_cast(locator.PackagesDir.c_str()), - const_cast(locator.ManifestPath.c_str()), - const_cast(locator.CurrentBinaryDir.c_str()), - locator.IsPortable - }; - } - - static inline vpkc_options_t to_vpkc(const UpdateOptions& options) { - return { - options.AllowVersionDowngrade, - const_cast(options.ExplicitChannel.c_str()) - }; - } - - static inline vpkc_asset_t to_vpkc(const VelopackAsset& asset) { - return { - const_cast(asset.PackageId.c_str()), - const_cast(asset.Version.c_str()), - const_cast(asset.Type.c_str()), - const_cast(asset.FileName.c_str()), - const_cast(asset.SHA1.c_str()), - const_cast(asset.SHA256.c_str()), - asset.Size, - const_cast(asset.NotesMarkdown.c_str()), - const_cast(asset.NotesHtml.c_str()) - }; - } - - static inline VelopackAsset from_vpkc(const vpkc_asset_t& asset) { - return { - asset.PackageId, - asset.Version, - asset.Type, - asset.FileName, - asset.SHA1, - asset.SHA256, - asset.Size, - asset.NotesMarkdown, - asset.NotesHtml - }; - } - - static inline vpkc_update_info_t to_vpkc(const UpdateInfo& update) { - return { - to_vpkc(update.TargetFullRelease), - update.IsDowngrade - }; - } - - static inline UpdateInfo from_vpkc(const vpkc_update_info_t& update) { - return { - from_vpkc(update.TargetFullRelease), - update.IsDowngrade - }; - } - - class VelopackApp { - private: - VelopackApp(); - public: - static VelopackApp Build() { - return VelopackApp(); - }; - VelopackApp& SetAutoApplyOnStartup(bool bAutoApply) { - vpkc_app_set_auto_apply_on_startup(bAutoApply); - return *this; - }; - VelopackApp& SetArgs(const std::vector& args) { - char** pArgs = new char*[args.size()]; - for (size_t i = 0; i < args.size(); i++) { - pArgs[i] = new char[args[i].size() + 1]; - strcpy_s(pArgs[i], args[i].size() + 1, args[i].c_str()); - } - vpkc_app_set_args(pArgs, args.size()); - - // Free all the memory - for (size_t i = 0; i < args.size(); i++) { - delete[] pArgs[i]; - } - delete[] pArgs; - return *this; - }; - VelopackApp& SetLocator(const VelopackLocator& locator) { - vpkc_locator_t vpkc_locator = to_vpkc(locator); - vpkc_app_set_locator(const_cast(&vpkc_locator)); - return *this; - }; - VelopackApp& OnAfterInstall(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_after_install(cbInstall); - return *this; - }; - VelopackApp& OnBeforeUninstall(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_before_uninstall(cbInstall); - return *this; - }; - VelopackApp& OnBeforeUpdate(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_before_update(cbInstall); - return *this; - }; - VelopackApp& OnAfterUpdate(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_after_update(cbInstall); - return *this; - }; - VelopackApp& OnFirstRun(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_first_run(cbInstall); - return *this; - }; - VelopackApp& OnRestarted(vpkc_hook_callback_t cbInstall) { - vpkc_app_set_hook_restarted(cbInstall); - return *this; - }; - void Run() { - vpkc_app_run(); - }; - }; - class UpdateManager { - private: - vpkc_update_manager_t m_pManager; - public: - UpdateManager(const std::string& urlOrPath, const UpdateOptions* options, const VelopackLocator* locator) { - vpkc_options_t* pOptions = nullptr; - if (options != nullptr) { - vpkc_options_t vpkc_options = to_vpkc(*options); - pOptions = const_cast(&vpkc_options); - } - - vpkc_locator_t* pLocator = nullptr; - if (locator != nullptr) { - vpkc_locator_t vpkc_locator = to_vpkc(*locator); - pLocator = const_cast(&vpkc_locator); - } - - if (0 != vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager)) { - throw_last_error(); - } - }; - ~UpdateManager() { - vpkc_free_update_manager(&m_pManager); - }; - bool IsPortable() noexcept { - return vpkc_is_portable(&m_pManager); - }; - std::string GetCurrentVersion() noexcept { - size_t neededSize = vpkc_get_current_version(&m_pManager, nullptr, 0); - std::string strVersion(neededSize, '\0'); - vpkc_get_current_version(&m_pManager, &strVersion[0], neededSize); - return strVersion; - }; - std::string GetAppId() noexcept { - size_t neededSize = vpkc_get_app_id(&m_pManager, nullptr, 0); - std::string strId(neededSize, '\0'); - vpkc_get_app_id(&m_pManager, &strId[0], neededSize); - return strId; - }; - std::optional UpdatePendingRestart() noexcept { - vpkc_asset_t asset; - if (vpkc_update_pending_restart(&m_pManager, &asset)) { - VelopackAsset cpp_asset = from_vpkc(asset); - vpkc_free_asset(&asset); - return cpp_asset; - } - return std::nullopt; - }; - std::optional CheckForUpdates() { - vpkc_update_info_t update; - vpkc_update_check_t result = vpkc_check_for_updates(&m_pManager, &update); - switch (result) { - case vpkc_update_check_t::ERROR: - throw_last_error(); - return std::nullopt; - case vpkc_update_check_t::NO_UPDATE_AVAILABLE: - return std::nullopt; - case vpkc_update_check_t::UPDATE_AVAILABLE: - UpdateInfo cpp_info = from_vpkc(update); - vpkc_free_update_info(&update); - return cpp_info; - } - }; - void DownloadUpdates(const UpdateInfo& update, vpkc_progress_callback_t progress) { - vpkc_update_info_t vpkc_update = to_vpkc(update); - if (!vpkc_download_updates(&m_pManager, &vpkc_update, progress)) { - throw_last_error(); - } - }; - void WaitExitThenApplyUpdate(const VelopackAsset& asset, bool silent, bool restart, std::vector restartArgs) { - char** pRestartArgs = new char*[restartArgs.size()]; - for (size_t i = 0; i < restartArgs.size(); i++) { - pRestartArgs[i] = new char[restartArgs[i].size() + 1]; - strcpy_s(pRestartArgs[i], restartArgs[i].size() + 1, restartArgs[i].c_str()); - } - - vpkc_asset_t vpkc_asset = to_vpkc(asset); - bool result = vpkc_wait_exit_then_apply_update(&m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size()); - - // Free all the memory - for (size_t i = 0; i < restartArgs.size(); i++) { - delete[] pRestartArgs[i]; - } - delete[] pRestartArgs; - - if (!result) { - throw_last_error(); - } - }; - void WaitExitThenApplyUpdate(const UpdateInfo& asset, bool silent, bool restart, std::vector restartArgs) { - this->WaitExitThenApplyUpdate(asset.TargetFullRelease, silent, restart, restartArgs); - }; +static inline void throw_last_error() { + size_t neededSize = vpkc_get_last_error(nullptr, 0); + std::string strError(neededSize, '\0'); + vpkc_get_last_error(&strError[0], neededSize); + throw std::runtime_error(strError); +} + +static inline std::string to_cppstring(const char* psz) { + return psz == nullptr ? "" : psz; +} + +static inline char* to_cstring(const std::string& str) { + return const_cast(str.c_str()); +} + +static inline char* to_cstring_opt(const std::optional& str) { + return str.has_value() ? to_cstring(str.value()) : nullptr; +} + +static inline std::optional to_cppstring_opt(const char* psz) { + return psz == nullptr ? std::nullopt : std::optional(psz); +} + +static inline bool to_cppbool(bool b) { return b; } +static inline bool to_cbool(bool b) { return b; } +static inline uint64_t to_cu64(uint64_t i) { return i; } +static inline uint64_t to_cppu64(uint64_t i) { return i; } + +// !! AUTO-GENERATED-START CPP_TYPES +struct VelopackLocatorConfig { + std::string RootAppDir; + std::string UpdateExePath; + std::string PackagesDir; + std::string ManifestPath; + std::string CurrentBinaryDir; + bool IsPortable; +}; + +static inline vpkc_locator_config_t to_c(const VelopackLocatorConfig& dto) { + return { + to_cstring(dto.RootAppDir), + to_cstring(dto.UpdateExePath), + to_cstring(dto.PackagesDir), + to_cstring(dto.ManifestPath), + to_cstring(dto.CurrentBinaryDir), + to_cbool(dto.IsPortable), }; } +static inline VelopackLocatorConfig to_cpp(const vpkc_locator_config_t& dto) { + return { + to_cppstring(dto.RootAppDir), + to_cppstring(dto.UpdateExePath), + to_cppstring(dto.PackagesDir), + to_cppstring(dto.ManifestPath), + to_cppstring(dto.CurrentBinaryDir), + to_cppbool(dto.IsPortable), + }; +} + +struct VelopackAsset { + std::string PackageId; + std::string Version; + std::string Type; + std::string FileName; + std::string SHA1; + std::string SHA256; + uint64_t Size; + std::string NotesMarkdown; + std::string NotesHtml; +}; + +static inline vpkc_asset_t to_c(const VelopackAsset& dto) { + return { + to_cstring(dto.PackageId), + to_cstring(dto.Version), + to_cstring(dto.Type), + to_cstring(dto.FileName), + to_cstring(dto.SHA1), + to_cstring(dto.SHA256), + to_cu64(dto.Size), + to_cstring(dto.NotesMarkdown), + to_cstring(dto.NotesHtml), + }; +} + +static inline VelopackAsset to_cpp(const vpkc_asset_t& dto) { + return { + to_cppstring(dto.PackageId), + to_cppstring(dto.Version), + to_cppstring(dto.Type), + to_cppstring(dto.FileName), + to_cppstring(dto.SHA1), + to_cppstring(dto.SHA256), + to_cppu64(dto.Size), + to_cppstring(dto.NotesMarkdown), + to_cppstring(dto.NotesHtml), + }; +} + +struct UpdateInfo { + VelopackAsset TargetFullRelease; + bool IsDowngrade; +}; + +static inline vpkc_update_info_t to_c(const UpdateInfo& dto) { + return { + to_c(dto.TargetFullRelease), + to_cbool(dto.IsDowngrade), + }; +} + +static inline UpdateInfo to_cpp(const vpkc_update_info_t& dto) { + return { + to_cpp(dto.TargetFullRelease), + to_cppbool(dto.IsDowngrade), + }; +} + +struct UpdateOptions { + bool AllowVersionDowngrade; + std::optional ExplicitChannel; +}; + +static inline vpkc_update_options_t to_c(const UpdateOptions& dto) { + return { + to_cbool(dto.AllowVersionDowngrade), + to_cstring_opt(dto.ExplicitChannel), + }; +} + +static inline UpdateOptions to_cpp(const vpkc_update_options_t& dto) { + return { + to_cppbool(dto.AllowVersionDowngrade), + to_cppstring_opt(dto.ExplicitChannel), + }; +} +// !! AUTO-GENERATED-END CPP_TYPES + +class VelopackApp { +private: + VelopackApp(); +public: + static VelopackApp Build() { + return VelopackApp(); + }; + VelopackApp& SetAutoApplyOnStartup(bool bAutoApply) { + vpkc_app_set_auto_apply_on_startup(bAutoApply); + return *this; + }; + VelopackApp& SetArgs(const std::vector& args) { + char** pArgs = new char*[args.size()]; + for (size_t i = 0; i < args.size(); i++) { + pArgs[i] = new char[args[i].size() + 1]; + strcpy_s(pArgs[i], args[i].size() + 1, args[i].c_str()); + } + vpkc_app_set_args(pArgs, args.size()); + + // Free all the memory + for (size_t i = 0; i < args.size(); i++) { + delete[] pArgs[i]; + } + delete[] pArgs; + return *this; + }; + VelopackApp& SetLocator(const VelopackLocatorConfig& locator) { + vpkc_locator_config_t vpkc_locator = to_c(locator); + vpkc_app_set_locator(&vpkc_locator); + return *this; + }; + VelopackApp& OnAfterInstall(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_after_install(cbInstall); + return *this; + }; + VelopackApp& OnBeforeUninstall(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_before_uninstall(cbInstall); + return *this; + }; + VelopackApp& OnBeforeUpdate(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_before_update(cbInstall); + return *this; + }; + VelopackApp& OnAfterUpdate(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_after_update(cbInstall); + return *this; + }; + VelopackApp& OnFirstRun(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_first_run(cbInstall); + return *this; + }; + VelopackApp& OnRestarted(vpkc_hook_callback_t cbInstall) { + vpkc_app_set_hook_restarted(cbInstall); + return *this; + }; + void Run() { + vpkc_app_run(); + }; +}; + +class UpdateManager { +private: + vpkc_update_manager_t m_pManager; +public: + UpdateManager(const std::string& urlOrPath, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) { + vpkc_update_options_t* pOptions = nullptr; + if (options != nullptr) { + vpkc_update_options_t vpkc_options = to_c(*options); + pOptions = &vpkc_options; + } + + vpkc_locator_config_t* pLocator = nullptr; + if (locator != nullptr) { + vpkc_locator_config_t vpkc_locator = to_c(*locator); + pLocator = &vpkc_locator; + } + + if (0 != vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager)) { + throw_last_error(); + } + }; + ~UpdateManager() { + vpkc_free_update_manager(&m_pManager); + }; + bool IsPortable() noexcept { + return vpkc_is_portable(&m_pManager); + }; + std::string GetCurrentVersion() noexcept { + size_t neededSize = vpkc_get_current_version(&m_pManager, nullptr, 0); + std::string strVersion(neededSize, '\0'); + vpkc_get_current_version(&m_pManager, &strVersion[0], neededSize); + return strVersion; + }; + std::string GetAppId() noexcept { + size_t neededSize = vpkc_get_app_id(&m_pManager, nullptr, 0); + std::string strId(neededSize, '\0'); + vpkc_get_app_id(&m_pManager, &strId[0], neededSize); + return strId; + }; + std::optional UpdatePendingRestart() noexcept { + vpkc_asset_t asset; + if (vpkc_update_pending_restart(&m_pManager, &asset)) { + VelopackAsset cpp_asset = to_cpp(asset); + vpkc_free_asset(&asset); + return cpp_asset; + } + return std::nullopt; + }; + std::optional CheckForUpdates() { + vpkc_update_info_t update; + vpkc_update_check_t result = vpkc_check_for_updates(&m_pManager, &update); + switch (result) { + case vpkc_update_check_t::ERROR: + throw_last_error(); + return std::nullopt; + case vpkc_update_check_t::NO_UPDATE_AVAILABLE: + return std::nullopt; + case vpkc_update_check_t::UPDATE_AVAILABLE: + UpdateInfo cpp_info = to_cpp(update); + vpkc_free_update_info(&update); + return cpp_info; + } + }; + void DownloadUpdates(const UpdateInfo& update, vpkc_progress_callback_t progress) { + vpkc_update_info_t vpkc_update = to_c(update); + if (!vpkc_download_updates(&m_pManager, &vpkc_update, progress)) { + throw_last_error(); + } + }; + void WaitExitThenApplyUpdate(const VelopackAsset& asset, bool silent, bool restart, std::vector restartArgs) { + char** pRestartArgs = new char*[restartArgs.size()]; + for (size_t i = 0; i < restartArgs.size(); i++) { + pRestartArgs[i] = new char[restartArgs[i].size() + 1]; + strcpy_s(pRestartArgs[i], restartArgs[i].size() + 1, restartArgs[i].c_str()); + } + + vpkc_asset_t vpkc_asset = to_c(asset); + bool result = vpkc_wait_exit_then_apply_update(&m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size()); + + // Free all the memory + for (size_t i = 0; i < restartArgs.size(); i++) { + delete[] pRestartArgs[i]; + } + delete[] pRestartArgs; + + if (!result) { + throw_last_error(); + } + }; + void WaitExitThenApplyUpdate(const UpdateInfo& asset, bool silent, bool restart, std::vector restartArgs) { + this->WaitExitThenApplyUpdate(asset.TargetFullRelease, silent, restart, restartArgs); + }; +}; + +} // namespace Velopack + #endif // __cplusplus #endif // VELOPACK_H \ No newline at end of file diff --git a/src/lib-cpp/src/bridge.cc b/src/lib-cpp/src/bridge.cc index fae1c0a2..0254b77c 100644 --- a/src/lib-cpp/src/bridge.cc +++ b/src/lib-cpp/src/bridge.cc @@ -1,5 +1,205 @@ +// Uncomment to enable debug type checking +// #pragma include_alias( "velopack_libc/src/lib.rs.h", "../../../target/cxxbridge/velopack_libc/src/lib.rs.h" ) +// #pragma include_alias( "velopack_libc/include/Velopack.h", "../include/Velopack.h" ) +// #pragma include_alias( "velopack_libc/src/bridge.hpp", "bridge.hpp" ) +// #pragma include_alias( "rust/cxx.h", "../../../target/cxxbridge/rust/cxx.h" ) + #include "velopack_libc/src/lib.rs.h" +static inline std::string to_bridgestring(const char* psz) { + return psz == nullptr ? "" : psz; +} + +static inline char* to_cstring(const std::string& str) { + return const_cast(str.c_str()); +} + +static inline char* to_cstring_opt(const std::optional& str) { + return str.has_value() ? to_cstring(str.value()) : nullptr; +} + +static inline StringOption to_bridgestring_opt(const char* psz) { + StringOption opt; + if (psz == nullptr) { + opt.has_data = false; + return opt; + } + opt.has_data = true; + opt.data = psz; + return opt; +} + +static inline void allocate_string(::rust::String& str, char** ppsz) { + *ppsz = _strdup(str.c_str()); +} + +static inline void allocate_string_opt(StringOption str, char** ppsz) { + if (str.has_data) { + *ppsz = _strdup(str.data.c_str()); + } else { + *ppsz = nullptr; + } +} + +// !! AUTO-GENERATED-START BRIDGE_MAPPING +static inline VelopackLocatorConfigDto to_bridge(vpkc_locator_config_t* pDto) { + if (pDto == nullptr) { return {}; } + return { + to_bridgestring(pDto->RootAppDir), + to_bridgestring(pDto->UpdateExePath), + to_bridgestring(pDto->PackagesDir), + to_bridgestring(pDto->ManifestPath), + to_bridgestring(pDto->CurrentBinaryDir), + pDto->IsPortable, + }; +} + +static inline VelopackLocatorConfigDtoOption to_bridge_opt(vpkc_locator_config_t* pDto) { + VelopackLocatorConfigDtoOption opt; + if (pDto == nullptr) { + opt.has_data = false; + return opt; + } + + opt.has_data = true; + opt.data = to_bridge(pDto); + return opt; +} + +static inline void allocate_velopacklocatorconfig(VelopackLocatorConfigDto bridgeDto, vpkc_locator_config_t* pDto) { + if (pDto == nullptr) { return; } + allocate_string(bridgeDto.RootAppDir, &pDto->RootAppDir); + allocate_string(bridgeDto.UpdateExePath, &pDto->UpdateExePath); + allocate_string(bridgeDto.PackagesDir, &pDto->PackagesDir); + allocate_string(bridgeDto.ManifestPath, &pDto->ManifestPath); + allocate_string(bridgeDto.CurrentBinaryDir, &pDto->CurrentBinaryDir); + pDto->IsPortable = bridgeDto.IsPortable; +} + +static inline void free_velopacklocatorconfig(vpkc_locator_config_t* pDto) { + if (pDto == nullptr) { return; } + free(pDto->RootAppDir); + free(pDto->UpdateExePath); + free(pDto->PackagesDir); + free(pDto->ManifestPath); + free(pDto->CurrentBinaryDir); +} + +static inline VelopackAssetDto to_bridge(vpkc_asset_t* pDto) { + if (pDto == nullptr) { return {}; } + return { + to_bridgestring(pDto->PackageId), + to_bridgestring(pDto->Version), + to_bridgestring(pDto->Type), + to_bridgestring(pDto->FileName), + to_bridgestring(pDto->SHA1), + to_bridgestring(pDto->SHA256), + pDto->Size, + to_bridgestring(pDto->NotesMarkdown), + to_bridgestring(pDto->NotesHtml), + }; +} + +static inline VelopackAssetDtoOption to_bridge_opt(vpkc_asset_t* pDto) { + VelopackAssetDtoOption opt; + if (pDto == nullptr) { + opt.has_data = false; + return opt; + } + + opt.has_data = true; + opt.data = to_bridge(pDto); + return opt; +} + +static inline void allocate_velopackasset(VelopackAssetDto bridgeDto, vpkc_asset_t* pDto) { + if (pDto == nullptr) { return; } + allocate_string(bridgeDto.PackageId, &pDto->PackageId); + allocate_string(bridgeDto.Version, &pDto->Version); + allocate_string(bridgeDto.Type, &pDto->Type); + allocate_string(bridgeDto.FileName, &pDto->FileName); + allocate_string(bridgeDto.SHA1, &pDto->SHA1); + allocate_string(bridgeDto.SHA256, &pDto->SHA256); + pDto->Size = bridgeDto.Size; + allocate_string(bridgeDto.NotesMarkdown, &pDto->NotesMarkdown); + allocate_string(bridgeDto.NotesHtml, &pDto->NotesHtml); +} + +static inline void free_velopackasset(vpkc_asset_t* pDto) { + if (pDto == nullptr) { return; } + free(pDto->PackageId); + free(pDto->Version); + free(pDto->Type); + free(pDto->FileName); + free(pDto->SHA1); + free(pDto->SHA256); + free(pDto->NotesMarkdown); + free(pDto->NotesHtml); +} + +static inline UpdateInfoDto to_bridge(vpkc_update_info_t* pDto) { + if (pDto == nullptr) { return {}; } + return { + to_bridge(&pDto->TargetFullRelease), + pDto->IsDowngrade, + }; +} + +static inline UpdateInfoDtoOption to_bridge_opt(vpkc_update_info_t* pDto) { + UpdateInfoDtoOption opt; + if (pDto == nullptr) { + opt.has_data = false; + return opt; + } + + opt.has_data = true; + opt.data = to_bridge(pDto); + return opt; +} + +static inline void allocate_updateinfo(UpdateInfoDto bridgeDto, vpkc_update_info_t* pDto) { + if (pDto == nullptr) { return; } + allocate_velopackasset(bridgeDto.TargetFullRelease, &pDto->TargetFullRelease); + pDto->IsDowngrade = bridgeDto.IsDowngrade; +} + +static inline void free_updateinfo(vpkc_update_info_t* pDto) { + if (pDto == nullptr) { return; } + free_velopackasset(&pDto->TargetFullRelease); +} + +static inline UpdateOptionsDto to_bridge(vpkc_update_options_t* pDto) { + if (pDto == nullptr) { return {}; } + return { + pDto->AllowVersionDowngrade, + to_bridgestring_opt(pDto->ExplicitChannel), + }; +} + +static inline UpdateOptionsDtoOption to_bridge_opt(vpkc_update_options_t* pDto) { + UpdateOptionsDtoOption opt; + if (pDto == nullptr) { + opt.has_data = false; + return opt; + } + + opt.has_data = true; + opt.data = to_bridge(pDto); + return opt; +} + +static inline void allocate_updateoptions(UpdateOptionsDto bridgeDto, vpkc_update_options_t* pDto) { + if (pDto == nullptr) { return; } + pDto->AllowVersionDowngrade = bridgeDto.AllowVersionDowngrade; + allocate_string_opt(bridgeDto.ExplicitChannel, &pDto->ExplicitChannel); +} + +static inline void free_updateoptions(vpkc_update_options_t* pDto) { + if (pDto == nullptr) { return; } + free(pDto->ExplicitChannel); +} +// !! AUTO-GENERATED-END BRIDGE_MAPPING + // Error handling char* lastError; LoggerCallbackManager logMgr{}; @@ -33,82 +233,13 @@ static inline void clear_last_error() { } } -static inline void copy_to_locator_option(vpkc_locator_t* pLocator, LocatorConfigOption& locator) { - if (pLocator) { - locator.has_data = true; - locator.data.RootAppDir = pLocator->RootAppDir; - locator.data.UpdateExePath = pLocator->UpdateExePath; - locator.data.PackagesDir = pLocator->PackagesDir; - locator.data.ManifestPath = pLocator->ManifestPath; - locator.data.CurrentBinaryDir = pLocator->CurrentBinaryDir; - locator.data.IsPortable = pLocator->IsPortable; - } else { - locator.has_data = false; - } -} - -static inline void copy_to_asset_dto(vpkc_asset_t* pAsset, AssetDto& asset) { - if (pAsset != nullptr) { - asset.PackageId = pAsset->PackageId; - asset.Version = pAsset->Version; - asset.Type = pAsset->Type; - asset.FileName = pAsset->FileName; - asset.SHA1 = pAsset->SHA1; - asset.SHA256 = pAsset->SHA256; - asset.NotesMarkdown = pAsset->NotesMarkdown; - asset.NotesHtml = pAsset->NotesHtml; - asset.Size = pAsset->Size; - } -} - -static inline void copy_to_update_info_dto(vpkc_update_info_t* pUpdate, UpdateInfoDto& update) { - if (pUpdate != nullptr) { - copy_to_asset_dto(&pUpdate->TargetFullRelease, update.TargetFullRelease); - update.IsDowngrade = pUpdate->IsDowngrade; - } -} - -static inline void copy_to_asset_pointer(AssetDto& asset, vpkc_asset_t* pAsset) { - if (pAsset != nullptr) { - pAsset->PackageId = _strdup(asset.PackageId.c_str()); - pAsset->Version = _strdup(asset.Version.c_str()); - pAsset->Type = _strdup(asset.Type.c_str()); - pAsset->FileName = _strdup(asset.FileName.c_str()); - pAsset->SHA1 = _strdup(asset.SHA1.c_str()); - pAsset->SHA256 = _strdup(asset.SHA256.c_str()); - pAsset->NotesMarkdown = _strdup(asset.NotesMarkdown.c_str()); - pAsset->NotesHtml = _strdup(asset.NotesHtml.c_str()); - pAsset->Size = asset.Size; - } -} - -static inline void copy_to_asset_pointer(AssetOption& asset, vpkc_asset_t* pAsset) { - if (asset.has_data && pAsset != nullptr) { - copy_to_asset_pointer(asset.data, pAsset); - } -} - -static inline void copy_to_update_info_pointer(UpdateInfoOption& update, vpkc_update_info_t* pUpdate) { - if (update.has_data && pUpdate != nullptr) { - copy_to_asset_pointer(update.data.TargetFullRelease, &pUpdate->TargetFullRelease); - pUpdate->IsDowngrade = update.data.IsDowngrade; - } -} - // Update Manager -VPKC_EXPORT bool VPKC_CALL vpkc_new_update_manager(const char* pszUrlOrString, const vpkc_options_t* pOptions, vpkc_locator_t* pLocator, vpkc_update_manager_t* pManager) { +VPKC_EXPORT bool VPKC_CALL vpkc_new_update_manager(const char* pszUrlOrString, vpkc_update_options_t* pOptions, vpkc_locator_config_t* pLocator, vpkc_update_manager_t* pManager) { clear_last_error(); try { - LocatorConfigOption locator{}; - UpdateOptionsDto options{}; - if (pOptions) { - options.AllowVersionDowngrade = pOptions->AllowVersionDowngrade; - if (pOptions->ExplicitChannel) { - options.ExplicitChannel = pOptions->ExplicitChannel; - } - } - copy_to_locator_option(pLocator, locator); - + VelopackLocatorConfigDtoOption locator = to_bridge_opt(pLocator); + UpdateOptionsDtoOption options = to_bridge_opt(pOptions); + ::rust::Box<::UpdateManagerOpaque> manager = bridge_new_update_manager(pszUrlOrString, options, locator); UpdateManagerOpaque* pOpaque = manager.into_raw(); *pManager = pOpaque; @@ -162,21 +293,20 @@ VPKC_EXPORT bool VPKC_CALL vpkc_is_portable(vpkc_update_manager_t* pManager) { } VPKC_EXPORT bool VPKC_CALL vpkc_update_pending_restart(vpkc_update_manager_t* pManager, vpkc_asset_t* pAsset) { UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); - AssetOption asset = bridge_update_pending_restart(*pOpaque); + VelopackAssetDtoOption asset = bridge_update_pending_restart(*pOpaque); if (asset.has_data) { - copy_to_asset_pointer(asset, pAsset); + allocate_velopackasset(asset.data, pAsset); return true; } return false; } - VPKC_EXPORT vpkc_update_check_t VPKC_CALL vpkc_check_for_updates(vpkc_update_manager_t* pManager, vpkc_update_info_t* pUpdate) { clear_last_error(); try { UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); - UpdateInfoOption update = bridge_check_for_updates(*pOpaque); + UpdateInfoDtoOption update = bridge_check_for_updates(*pOpaque); if (update.has_data) { - copy_to_update_info_pointer(update, pUpdate); + allocate_updateinfo(update.data, pUpdate); return vpkc_update_check_t::UPDATE_AVAILABLE; } return vpkc_update_check_t::NO_UPDATE_AVAILABLE; @@ -189,15 +319,13 @@ VPKC_EXPORT vpkc_update_check_t VPKC_CALL vpkc_check_for_updates(vpkc_update_man VPKC_EXPORT bool VPKC_CALL vpkc_download_updates(vpkc_update_manager_t* pManager, vpkc_update_info_t* pUpdate, vpkc_progress_callback_t cbProgress) { clear_last_error(); try { - UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); - UpdateInfoDto update{}; - if (!pUpdate) { throw new std::runtime_error("pUpdate is a required parameter"); } - - copy_to_update_info_dto(pUpdate, update); - + + UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); + UpdateInfoDto update = to_bridge(pUpdate); + DownloadCallbackManager download{}; download.progress_cb = cbProgress; bridge_download_updates(*pOpaque, update, download); @@ -212,15 +340,13 @@ VPKC_EXPORT bool VPKC_CALL vpkc_download_updates(vpkc_update_manager_t* pManager VPKC_EXPORT bool VPKC_CALL vpkc_wait_exit_then_apply_update(vpkc_update_manager_t* pManager, vpkc_asset_t* pAsset, bool bSilent, bool bRestart, char** pRestartArgs, size_t cRestartArgs) { clear_last_error(); try { - UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); - AssetDto asset{}; - if (!pAsset) { throw new std::runtime_error("pAsset is a required parameter"); } - - copy_to_asset_dto(pAsset, asset); - + + UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); + VelopackAssetDto asset = to_bridge(pAsset); + ::rust::Vec<::rust::String> restartArgs{}; for (size_t i = 0; i < cRestartArgs; i++) { restartArgs.push_back(pRestartArgs[i]); @@ -238,7 +364,7 @@ VPKC_EXPORT bool VPKC_CALL vpkc_wait_exit_then_apply_update(vpkc_update_manager_ // VelopackApp bool autoApply = true; StringArrayOption args{}; -LocatorConfigOption locator{}; +VelopackLocatorConfigDtoOption locator{}; HookCallbackManager hooks{}; VPKC_EXPORT void VPKC_CALL vpkc_app_set_auto_apply_on_startup(bool bAutoApply) { @@ -251,8 +377,8 @@ VPKC_EXPORT void VPKC_CALL vpkc_app_set_args(char** pArgs, size_t cArgs) { args.data.push_back(pArgs[i]); } } -VPKC_EXPORT void VPKC_CALL vpkc_app_set_locator(vpkc_locator_t* pLocator) { - copy_to_locator_option(pLocator, locator); +VPKC_EXPORT void VPKC_CALL vpkc_app_set_locator(vpkc_locator_config_t* pLocator) { + locator = to_bridge_opt(pLocator); } VPKC_EXPORT void VPKC_CALL vpkc_app_set_hook_after_install(vpkc_hook_callback_t cbAfterInstall) { hooks.after_install = cbAfterInstall; @@ -284,45 +410,11 @@ VPKC_EXPORT void VPKC_CALL vpkc_set_log(vpkc_log_callback_t cbLog) { VPKC_EXPORT void VPKC_CALL vpkc_free_update_manager(vpkc_update_manager_t* pManager) { UpdateManagerOpaque* pOpaque = reinterpret_cast(*pManager); auto box = ::rust::Box<::UpdateManagerOpaque>::from_raw(pOpaque); + // this will free when the box goes out of scope } VPKC_EXPORT void VPKC_CALL vpkc_free_update_info(vpkc_update_info_t* pUpdateInfo) { - if (pUpdateInfo != nullptr) { - vpkc_free_asset(&pUpdateInfo->TargetFullRelease); - } + free_updateinfo(pUpdateInfo); } VPKC_EXPORT void VPKC_CALL vpkc_free_asset(vpkc_asset_t* pAsset) { - if (pAsset != nullptr) { - if (pAsset->PackageId) { - free(pAsset->PackageId); - pAsset->PackageId = nullptr; - } - if (pAsset->Version) { - free(pAsset->Version); - pAsset->Version = nullptr; - } - if (pAsset->Type) { - free(pAsset->Type); - pAsset->Type = nullptr; - } - if (pAsset->FileName) { - free(pAsset->FileName); - pAsset->FileName = nullptr; - } - if (pAsset->SHA1) { - free(pAsset->SHA1); - pAsset->SHA1 = nullptr; - } - if (pAsset->SHA256) { - free(pAsset->SHA256); - pAsset->SHA256 = nullptr; - } - if (pAsset->NotesMarkdown) { - free(pAsset->NotesMarkdown); - pAsset->NotesMarkdown = nullptr; - } - if (pAsset->NotesHtml) { - free(pAsset->NotesHtml); - pAsset->NotesHtml = nullptr; - } - } + free_velopackasset(pAsset); } \ No newline at end of file diff --git a/src/lib-cpp/src/lib.rs b/src/lib-cpp/src/lib.rs index ebd4daf0..b565afdb 100644 --- a/src/lib-cpp/src/lib.rs +++ b/src/lib-cpp/src/lib.rs @@ -1,26 +1,47 @@ #![allow(non_snake_case)] +mod map; +use map::*; + use anyhow::{bail, Result}; -use std::path::PathBuf; -use std::sync::atomic::{AtomicUsize, Ordering}; use log::{Level, Log, Metadata, Record}; -use velopack::{ - locator::VelopackLocatorConfig, - UpdateManager, - UpdateCheck, - UpdateOptions as VelopackUpdateOptions, - UpdateInfo as VelopackUpdateInfo, - VelopackAsset, - VelopackApp, - Error as VelopackError, - sources, -}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use velopack::{sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp}; #[cxx::bridge] mod ffi { // Shared structs with fields visible to both languages. #[derive(Default)] - pub struct AssetDto { + pub struct StringOption { + pub data: String, + pub has_data: bool, + } + + #[derive(Default)] + pub struct StringArrayOption { + pub data: Vec, + pub has_data: bool, + } + + // !! AUTO-GENERATED-START BRIDGE_DTOS + #[derive(Default)] + pub struct VelopackLocatorConfigDto { + pub RootAppDir: String, + pub UpdateExePath: String, + pub PackagesDir: String, + pub ManifestPath: String, + pub CurrentBinaryDir: String, + pub IsPortable: bool, + } + + #[derive(Default)] + pub struct VelopackLocatorConfigDtoOption { + pub data: VelopackLocatorConfigDto, + pub has_data: bool, + } + + #[derive(Default)] + pub struct VelopackAssetDto { pub PackageId: String, pub Version: String, pub Type: String, @@ -32,50 +53,42 @@ mod ffi { pub NotesHtml: String, } - pub struct AssetOption { - pub data: AssetDto, + #[derive(Default)] + pub struct VelopackAssetDtoOption { + pub data: VelopackAssetDto, pub has_data: bool, } #[derive(Default)] pub struct UpdateInfoDto { - pub TargetFullRelease: AssetDto, + pub TargetFullRelease: VelopackAssetDto, pub IsDowngrade: bool, } - pub struct UpdateInfoOption { + #[derive(Default)] + pub struct UpdateInfoDtoOption { pub data: UpdateInfoDto, pub has_data: bool, } - pub struct LocatorConfigDto { - pub RootAppDir: String, - pub UpdateExePath: String, - pub PackagesDir: String, - pub ManifestPath: String, - pub CurrentBinaryDir: String, - pub IsPortable: bool, - } - - pub struct LocatorConfigOption { - pub data: LocatorConfigDto, - pub has_data: bool, - } - + #[derive(Default)] pub struct UpdateOptionsDto { pub AllowVersionDowngrade: bool, - pub ExplicitChannel: String, + pub ExplicitChannel: StringOption, } - pub struct StringArrayOption { - pub data: Vec, + #[derive(Default)] + pub struct UpdateOptionsDtoOption { + pub data: UpdateOptionsDto, pub has_data: bool, } + // !! AUTO-GENERATED-END BRIDGE_DTOS // C++ types and signatures exposed to Rust. unsafe extern "C++" { include!("velopack_libc/include/Velopack.h"); include!("velopack_libc/src/bridge.hpp"); + type HookCallbackManager; fn install_hook(self: &HookCallbackManager, app_version: String); fn update_hook(self: &HookCallbackManager, app_version: String); @@ -83,8 +96,10 @@ mod ffi { fn uninstall_hook(self: &HookCallbackManager, app_version: String); fn firstrun_hook(self: &HookCallbackManager, app_version: String); fn restarted_hook(self: &HookCallbackManager, app_version: String); + type DownloadCallbackManager; fn download_progress(self: &DownloadCallbackManager, progress: i16); + type LoggerCallbackManager; fn log(self: &LoggerCallbackManager, level: String, message: String); } @@ -92,15 +107,34 @@ mod ffi { // Rust types and signatures exposed to C++. extern "Rust" { type UpdateManagerOpaque; - fn bridge_new_update_manager(url_or_path: &String, options: &UpdateOptionsDto, locator: &LocatorConfigOption) -> Result>; + fn bridge_new_update_manager( + url_or_path: &String, + options: &UpdateOptionsDtoOption, + locator: &VelopackLocatorConfigDtoOption, + ) -> Result>; fn bridge_get_current_version(manager: &UpdateManagerOpaque) -> String; fn bridge_get_app_id(manager: &UpdateManagerOpaque) -> String; fn bridge_is_portable(manager: &UpdateManagerOpaque) -> bool; - fn bridge_update_pending_restart(manager: &UpdateManagerOpaque) -> AssetOption; - fn bridge_check_for_updates(manager: &UpdateManagerOpaque) -> Result; - fn bridge_download_updates(manager: &UpdateManagerOpaque, to_download: &UpdateInfoDto, progress: &DownloadCallbackManager) -> Result<()>; - fn bridge_wait_exit_then_apply_update(manager: &UpdateManagerOpaque, to_apply: &AssetDto, silent: bool, restart: bool, restart_args: &Vec) -> Result<()>; - fn bridge_appbuilder_run(cb: &HookCallbackManager, custom_args: &StringArrayOption, locator: &LocatorConfigOption, auto_apply: bool); + fn bridge_update_pending_restart(manager: &UpdateManagerOpaque) -> VelopackAssetDtoOption; + fn bridge_check_for_updates(manager: &UpdateManagerOpaque) -> Result; + fn bridge_download_updates( + manager: &UpdateManagerOpaque, + to_download: &UpdateInfoDto, + progress: &DownloadCallbackManager, + ) -> Result<()>; + fn bridge_wait_exit_then_apply_update( + manager: &UpdateManagerOpaque, + to_apply: &VelopackAssetDto, + silent: bool, + restart: bool, + restart_args: &Vec, + ) -> Result<()>; + fn bridge_appbuilder_run( + cb: &HookCallbackManager, + custom_args: &StringArrayOption, + locator: &VelopackLocatorConfigDtoOption, + auto_apply: bool, + ); unsafe fn bridge_set_logger_callback(cb: *mut LoggerCallbackManager); } } @@ -110,79 +144,16 @@ struct UpdateManagerOpaque { obj: UpdateManager, } -fn to_locator_config(locator: &ffi::LocatorConfigDto) -> VelopackLocatorConfig { - VelopackLocatorConfig { - RootAppDir: PathBuf::from(locator.RootAppDir.clone()), - UpdateExePath: PathBuf::from(locator.UpdateExePath.clone()), - PackagesDir: PathBuf::from(locator.PackagesDir.clone()), - ManifestPath: PathBuf::from(locator.ManifestPath.clone()), - CurrentBinaryDir: PathBuf::from(locator.CurrentBinaryDir.clone()), - IsPortable: locator.IsPortable, - } -} - -fn to_update_options(options: &ffi::UpdateOptionsDto) -> VelopackUpdateOptions { - let channel = options.ExplicitChannel.clone(); - VelopackUpdateOptions { - AllowVersionDowngrade: options.AllowVersionDowngrade, - ExplicitChannel: if channel.is_empty() { None } else { Some(channel) }, - } -} - -fn from_asset(asset: &VelopackAsset) -> ffi::AssetDto { - ffi::AssetDto { - PackageId: asset.PackageId.clone(), - Version: asset.Version.clone(), - Type: asset.Type.clone(), - FileName: asset.FileName.clone(), - SHA1: asset.SHA1.clone(), - SHA256: asset.SHA256.clone(), - Size: asset.Size, - NotesMarkdown: asset.NotesMarkdown.clone(), - NotesHtml: asset.NotesHtml.clone(), - } -} - -fn to_asset(asset: &ffi::AssetDto) -> VelopackAsset { - VelopackAsset { - PackageId: asset.PackageId.clone(), - Version: asset.Version.clone(), - Type: asset.Type.clone(), - FileName: asset.FileName.clone(), - SHA1: asset.SHA1.clone(), - SHA256: asset.SHA256.clone(), - Size: asset.Size, - NotesMarkdown: asset.NotesMarkdown.clone(), - NotesHtml: asset.NotesHtml.clone(), - } -} - -fn from_update_info(info: &VelopackUpdateInfo) -> ffi::UpdateInfoDto { - ffi::UpdateInfoDto { - TargetFullRelease: from_asset(&info.TargetFullRelease), - IsDowngrade: info.IsDowngrade, - } -} - -fn to_update_info(info: &ffi::UpdateInfoDto) -> VelopackUpdateInfo { - VelopackUpdateInfo { - TargetFullRelease: to_asset(&info.TargetFullRelease), - IsDowngrade: info.IsDowngrade, - } -} - -fn bridge_new_update_manager(url_or_path: &String, options: &ffi::UpdateOptionsDto, locator: &ffi::LocatorConfigOption) -> Result> { +fn bridge_new_update_manager( + url_or_path: &String, + options: &ffi::UpdateOptionsDtoOption, + locator: &ffi::VelopackLocatorConfigDtoOption, +) -> Result> { let source = sources::AutoSource::new(url_or_path); - let update_options = to_update_options(&options); - - if locator.has_data { - let locator_config = to_locator_config(&locator.data); - let update_manager = UpdateManager::new(source, Some(update_options), Some(locator_config))?; - Ok(Box::new(UpdateManagerOpaque { obj: update_manager })) - } else { - let update_manager = UpdateManager::new(source, Some(update_options), None)?; - Ok(Box::new(UpdateManagerOpaque { obj: update_manager })) - } + let options = updateoptions_to_core_option(options); + let locator = velopacklocatorconfig_to_core_option(locator); + let update_manager = UpdateManager::new(source, options, locator)?; + Ok(Box::new(UpdateManagerOpaque { obj: update_manager })) } fn bridge_get_current_version(manager: &UpdateManagerOpaque) -> String { @@ -197,29 +168,22 @@ fn bridge_is_portable(manager: &UpdateManagerOpaque) -> bool { manager.obj.get_is_portable() } -fn bridge_update_pending_restart(manager: &UpdateManagerOpaque) -> ffi::AssetOption { - if let Some(info) = manager.obj.get_update_pending_restart() { - let update = from_asset(&info); - ffi::AssetOption { data: update, has_data: true } - } else { - let default = ffi::AssetDto::default(); - ffi::AssetOption { data: default, has_data: false } - } +fn bridge_update_pending_restart(manager: &UpdateManagerOpaque) -> ffi::VelopackAssetDtoOption { + let asset_opt = manager.obj.get_update_pending_restart(); + velopackasset_to_bridge_option(&asset_opt) } -fn bridge_check_for_updates(manager: &UpdateManagerOpaque) -> Result { - if let UpdateCheck::UpdateAvailable(info) = manager.obj.check_for_updates()? { - let update = from_update_info(&info); - Ok(ffi::UpdateInfoOption { data: update, has_data: true }) - } else { - let default = ffi::UpdateInfoDto::default(); - Ok(ffi::UpdateInfoOption { data: default, has_data: false }) - } +fn bridge_check_for_updates(manager: &UpdateManagerOpaque) -> Result { + let info_opt = if let UpdateCheck::UpdateAvailable(info) = manager.obj.check_for_updates()? { Some(info) } else { None }; + Ok(updateinfo_to_bridge_option(&info_opt)) } -fn bridge_download_updates(manager: &UpdateManagerOpaque, to_download: &ffi::UpdateInfoDto, cb: &ffi::DownloadCallbackManager) -> Result<()> { - let info = to_update_info(&to_download); - +fn bridge_download_updates( + manager: &UpdateManagerOpaque, + to_download: &ffi::UpdateInfoDto, + cb: &ffi::DownloadCallbackManager, +) -> Result<()> { + let info = updateinfo_to_core(&to_download); let (progress_sender, progress_receiver) = std::sync::mpsc::channel::(); let (completion_sender, completion_receiver) = std::sync::mpsc::channel::>(); @@ -260,13 +224,24 @@ fn bridge_download_updates(manager: &UpdateManagerOpaque, to_download: &ffi::Upd } } -fn bridge_wait_exit_then_apply_update(manager: &UpdateManagerOpaque, to_apply: &ffi::AssetDto, silent: bool, restart: bool, restart_args: &Vec) -> Result<()> { - let asset = to_asset(&to_apply); +fn bridge_wait_exit_then_apply_update( + manager: &UpdateManagerOpaque, + to_apply: &ffi::VelopackAssetDto, + silent: bool, + restart: bool, + restart_args: &Vec, +) -> Result<()> { + let asset = velopackasset_to_core(&to_apply); manager.obj.wait_exit_then_apply_updates(&asset, silent, restart, restart_args)?; Ok(()) } -fn bridge_appbuilder_run(cb: &ffi::HookCallbackManager, custom_args: &ffi::StringArrayOption, locator: &ffi::LocatorConfigOption, auto_apply: bool) { +fn bridge_appbuilder_run( + cb: &ffi::HookCallbackManager, + custom_args: &ffi::StringArrayOption, + locator: &ffi::VelopackLocatorConfigDtoOption, + auto_apply: bool, +) { let mut app = VelopackApp::build() .set_auto_apply_on_startup(auto_apply) .on_first_run(|v| cb.firstrun_hook(v.to_string())) @@ -282,7 +257,8 @@ fn bridge_appbuilder_run(cb: &ffi::HookCallbackManager, custom_args: &ffi::Strin } if locator.has_data { - app = app.set_locator(to_locator_config(&locator.data)); + let locator = velopacklocatorconfig_to_core(&locator.data); + app = app.set_locator(locator); } if custom_args.has_data { @@ -314,7 +290,8 @@ impl Log for LoggerImpl { Level::Info => "info", Level::Debug => "debug", Level::Trace => "trace", - }.to_string(); + } + .to_string(); if let Some(cb) = get_logger() { if let Some(cb) = unsafe { cb.as_mut() } { @@ -347,4 +324,4 @@ unsafe fn bridge_set_logger_callback(cb: *mut ffi::LoggerCallbackManager) { let _ = log::set_logger(&LOGGER); log::set_max_level(log::LevelFilter::Trace); store_logger(cb); -} \ No newline at end of file +} diff --git a/src/lib-cpp/src/map.rs b/src/lib-cpp/src/map.rs new file mode 100644 index 00000000..3c68fece --- /dev/null +++ b/src/lib-cpp/src/map.rs @@ -0,0 +1,162 @@ +#![allow(dead_code)] + +use std::path::PathBuf; +use velopack::locator::VelopackLocatorConfig; +use velopack::{UpdateInfo, UpdateOptions, VelopackAsset}; +use crate::ffi::*; + +fn pathbuf_to_core(dto: &String) -> PathBuf { + PathBuf::from(dto) +} + +fn pathbuf_to_bridge(dto: &PathBuf) -> String { + dto.to_string_lossy().to_string() +} + +fn string_to_core(dto: &String) -> String { + dto.clone() +} + +fn string_to_bridge(dto: &String) -> String { + dto.clone() +} + +fn bool_to_core(dto: &bool) -> bool { + *dto +} + +fn bool_to_bridge(dto: &bool) -> bool { + *dto +} + +fn u64_to_core(dto: &u64) -> u64 { + *dto +} + +fn u64_to_bridge(dto: &u64) -> u64 { + *dto +} + +// !! AUTO-GENERATED-START CORE_MAPPING +pub fn velopacklocatorconfig_to_core(dto: &VelopackLocatorConfigDto) -> VelopackLocatorConfig { + VelopackLocatorConfig { + RootAppDir: pathbuf_to_core(&dto.RootAppDir), + UpdateExePath: pathbuf_to_core(&dto.UpdateExePath), + PackagesDir: pathbuf_to_core(&dto.PackagesDir), + ManifestPath: pathbuf_to_core(&dto.ManifestPath), + CurrentBinaryDir: pathbuf_to_core(&dto.CurrentBinaryDir), + IsPortable: bool_to_core(&dto.IsPortable), + } +} + +pub fn velopacklocatorconfig_to_bridge(dto: &VelopackLocatorConfig) -> VelopackLocatorConfigDto { + VelopackLocatorConfigDto { + RootAppDir: pathbuf_to_bridge(&dto.RootAppDir), + UpdateExePath: pathbuf_to_bridge(&dto.UpdateExePath), + PackagesDir: pathbuf_to_bridge(&dto.PackagesDir), + ManifestPath: pathbuf_to_bridge(&dto.ManifestPath), + CurrentBinaryDir: pathbuf_to_bridge(&dto.CurrentBinaryDir), + IsPortable: bool_to_bridge(&dto.IsPortable), + } +} + +pub fn velopacklocatorconfig_to_core_option(dto: &VelopackLocatorConfigDtoOption) -> Option { + if dto.has_data { Some(velopacklocatorconfig_to_core(&dto.data)) } else { None } +} + +pub fn velopacklocatorconfig_to_bridge_option(dto: &Option) -> VelopackLocatorConfigDtoOption { + match dto { + Some(dto) => VelopackLocatorConfigDtoOption { data: velopacklocatorconfig_to_bridge(dto), has_data: true }, + None => VelopackLocatorConfigDtoOption { data: Default::default(), has_data: false }, + } +} + +pub fn velopackasset_to_core(dto: &VelopackAssetDto) -> VelopackAsset { + VelopackAsset { + PackageId: string_to_core(&dto.PackageId), + Version: string_to_core(&dto.Version), + Type: string_to_core(&dto.Type), + FileName: string_to_core(&dto.FileName), + SHA1: string_to_core(&dto.SHA1), + SHA256: string_to_core(&dto.SHA256), + Size: u64_to_core(&dto.Size), + NotesMarkdown: string_to_core(&dto.NotesMarkdown), + NotesHtml: string_to_core(&dto.NotesHtml), + } +} + +pub fn velopackasset_to_bridge(dto: &VelopackAsset) -> VelopackAssetDto { + VelopackAssetDto { + PackageId: string_to_bridge(&dto.PackageId), + Version: string_to_bridge(&dto.Version), + Type: string_to_bridge(&dto.Type), + FileName: string_to_bridge(&dto.FileName), + SHA1: string_to_bridge(&dto.SHA1), + SHA256: string_to_bridge(&dto.SHA256), + Size: u64_to_bridge(&dto.Size), + NotesMarkdown: string_to_bridge(&dto.NotesMarkdown), + NotesHtml: string_to_bridge(&dto.NotesHtml), + } +} + +pub fn velopackasset_to_core_option(dto: &VelopackAssetDtoOption) -> Option { + if dto.has_data { Some(velopackasset_to_core(&dto.data)) } else { None } +} + +pub fn velopackasset_to_bridge_option(dto: &Option) -> VelopackAssetDtoOption { + match dto { + Some(dto) => VelopackAssetDtoOption { data: velopackasset_to_bridge(dto), has_data: true }, + None => VelopackAssetDtoOption { data: Default::default(), has_data: false }, + } +} + +pub fn updateinfo_to_core(dto: &UpdateInfoDto) -> UpdateInfo { + UpdateInfo { + TargetFullRelease: velopackasset_to_core(&dto.TargetFullRelease), + IsDowngrade: bool_to_core(&dto.IsDowngrade), + } +} + +pub fn updateinfo_to_bridge(dto: &UpdateInfo) -> UpdateInfoDto { + UpdateInfoDto { + TargetFullRelease: velopackasset_to_bridge(&dto.TargetFullRelease), + IsDowngrade: bool_to_bridge(&dto.IsDowngrade), + } +} + +pub fn updateinfo_to_core_option(dto: &UpdateInfoDtoOption) -> Option { + if dto.has_data { Some(updateinfo_to_core(&dto.data)) } else { None } +} + +pub fn updateinfo_to_bridge_option(dto: &Option) -> UpdateInfoDtoOption { + match dto { + Some(dto) => UpdateInfoDtoOption { data: updateinfo_to_bridge(dto), has_data: true }, + None => UpdateInfoDtoOption { data: Default::default(), has_data: false }, + } +} + +pub fn updateoptions_to_core(dto: &UpdateOptionsDto) -> UpdateOptions { + UpdateOptions { + AllowVersionDowngrade: bool_to_core(&dto.AllowVersionDowngrade), + ExplicitChannel: if dto.ExplicitChannel.has_data { Some(string_to_core(&dto.ExplicitChannel.data)) } else { None }, + } +} + +pub fn updateoptions_to_bridge(dto: &UpdateOptions) -> UpdateOptionsDto { + UpdateOptionsDto { + AllowVersionDowngrade: bool_to_bridge(&dto.AllowVersionDowngrade), + ExplicitChannel: StringOption { data: string_to_bridge(&dto.ExplicitChannel.clone().unwrap_or_default()), has_data: dto.ExplicitChannel.is_some() }, + } +} + +pub fn updateoptions_to_core_option(dto: &UpdateOptionsDtoOption) -> Option { + if dto.has_data { Some(updateoptions_to_core(&dto.data)) } else { None } +} + +pub fn updateoptions_to_bridge_option(dto: &Option) -> UpdateOptionsDtoOption { + match dto { + Some(dto) => UpdateOptionsDtoOption { data: updateoptions_to_bridge(dto), has_data: true }, + None => UpdateOptionsDtoOption { data: Default::default(), has_data: false }, + } +} +// !! AUTO-GENERATED-END CORE_MAPPING \ No newline at end of file diff --git a/src/lib-cpp/type-generator/IndentStringBuilder.cs b/src/lib-cpp/type-generator/IndentStringBuilder.cs new file mode 100644 index 00000000..e5fb45be --- /dev/null +++ b/src/lib-cpp/type-generator/IndentStringBuilder.cs @@ -0,0 +1,52 @@ +using System.Text; + +public class IndentStringBuilder +{ + private StringBuilder _sb = new(); + int _indent = 0; + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string text) + { + AppendIndent(); + _sb.AppendLine(text); + } + + private void AppendIndent() + { + _sb.Append(' ', _indent * 4); + } + + public IDisposable Indent() + { + _indent++; + return new IndentDisposable(this); + } + + private void RemoveIndent() + { + _indent--; + } + + public override string ToString() + { + return _sb.ToString(); + } + + private class IndentDisposable(IndentStringBuilder isb) : IDisposable + { + private bool _disposed = false; + + public void Dispose() + { + if (!_disposed) { + isb.RemoveIndent(); + _disposed = true; + } + } + } +} \ No newline at end of file diff --git a/src/lib-cpp/type-generator/Program.cs b/src/lib-cpp/type-generator/Program.cs new file mode 100644 index 00000000..8a41031f --- /dev/null +++ b/src/lib-cpp/type-generator/Program.cs @@ -0,0 +1,92 @@ +using System.Reflection; + +var scriptsDir = Assembly.GetEntryAssembly()! + .GetCustomAttributes() + .Single(x => x.Key == "SelfDir").Value!; + +var librustDir = Path.Combine(scriptsDir, "..", "..", "lib-rust", "src"); +var libcppDir = Path.Combine(scriptsDir, ".."); +var files = Directory.EnumerateFiles(librustDir, "*.rs", SearchOption.AllDirectories); + +string[] desiredStructs = [ + "VelopackAsset", + "UpdateInfo", + "UpdateOptions", + "VelopackLocatorConfig", +]; + +Dictionary basic_libc_names = new() { + { "VelopackAsset", "vpkc_asset_t" }, + { "UpdateInfo", "vpkc_update_info_t" }, + { "UpdateOptions", "vpkc_update_options_t" }, + { "VelopackLocatorConfig", "vpkc_locator_config_t" }, +}; + +List availableStructs = new(); +string[] searchStrings = desiredStructs.Select(s => "struct " + s + " {").ToArray(); + +foreach (var file in files) { + Console.WriteLine(file); + var text = File.ReadAllText(file); + + var structs = StructFinder.FindStructs(text); + + foreach (var s in structs) { + if (searchStrings.Any(search => s.Contains(search))) { + var result = StructParser.ParseStructs(s); + availableStructs.AddRange(result); + } + } +} + +if (desiredStructs.Length != availableStructs.Count) { + Console.WriteLine("Not all structs were found."); + Console.WriteLine("Desired structs: " + string.Join(", ", desiredStructs)); + Console.WriteLine("Available structs: " + string.Join(", ", availableStructs.Select(s => s.Name))); + return -1; +} + +// rust bridge code +string rustCppLib = Path.Combine(libcppDir, "src", "lib.rs"); +string rustCppMap = Path.Combine(libcppDir, "src", "map.rs"); +string rustCppInclude = Path.Combine(libcppDir, "include", "Velopack.h"); +string rustBridgeC = Path.Combine(libcppDir, "src", "bridge.cc"); + +Console.WriteLine("Generating bridge dtos"); +var sbBridgeDto = new IndentStringBuilder(); +foreach(var rs in availableStructs) { + Templates.WriteBridgeDto(desiredStructs, sbBridgeDto, rs); +} + +Console.WriteLine("Generating bridge to core mappings"); +var sbBridgeMapping = new IndentStringBuilder(); +foreach(var rs in availableStructs) { + Templates.WriteBridgeToCoreMapping(desiredStructs, sbBridgeMapping, rs); +} + +Console.WriteLine("Generating C types"); +var cTypes = new IndentStringBuilder(); +foreach(var rs in availableStructs) { + Templates.WriteBasicC(basic_libc_names, cTypes, rs); +} + +Console.WriteLine("Generating C++ types"); +var cppTypes = new IndentStringBuilder(); +foreach(var rs in availableStructs) { + Templates.WriteCPlusPlus(basic_libc_names, cppTypes, rs); +} + +Console.WriteLine("Generating C to bridge mappings"); +var cToBridgeMapping = new IndentStringBuilder(); +foreach(var rs in availableStructs) { + Templates.WriteCBridgeMapping(basic_libc_names, cToBridgeMapping, rs); +} + +Console.WriteLine("Writing all to file"); +Util.ReplaceTextInFile(rustCppLib, "BRIDGE_DTOS", sbBridgeDto.ToString()); +Util.ReplaceTextInFile(rustCppMap, "CORE_MAPPING", sbBridgeMapping.ToString()); +Util.ReplaceTextInFile(rustCppInclude, "C_TYPES", cTypes.ToString()); +Util.ReplaceTextInFile(rustCppInclude, "CPP_TYPES", cppTypes.ToString()); +Util.ReplaceTextInFile(rustBridgeC, "BRIDGE_MAPPING", cToBridgeMapping.ToString()); + +return 0; \ No newline at end of file diff --git a/src/lib-cpp/type-generator/StructFinder.cs b/src/lib-cpp/type-generator/StructFinder.cs new file mode 100644 index 00000000..ef6c7541 --- /dev/null +++ b/src/lib-cpp/type-generator/StructFinder.cs @@ -0,0 +1,45 @@ +using System.Text.RegularExpressions; + +public static class StructFinder +{ + public static string[] FindStructs(string code) + { + List structs = new(); + code = code.ReplaceLineEndings("\n"); + foreach (var match in Regex.EnumerateMatches(code, @"(^|[^\S\r\n]+)(pub\s*)?struct")) { + var linesBefore = code.Substring(0, match.Index).Split(new char[] { '\n' }, StringSplitOptions.None); + + var beginLine = linesBefore + .Select((Text, Index) => new { Text, Index }) + .Last(p => String.IsNullOrWhiteSpace(p.Text)) + .Index + 1; + + var startIndex = linesBefore + .Take(beginLine) + .Aggregate(0, (i, s) => i + s.Length + 1); + + string textAfterStart = code.Substring(startIndex); + + int firstOpenBrace = textAfterStart.IndexOf('{'); + int numOpenBraces = 1; + + // Find the end of the struct by counting open and close braces until numOpenBraces == 0 + for (int i = firstOpenBrace + 1; i < textAfterStart.Length; i++) { + if (textAfterStart[i] == '{') { + numOpenBraces++; + } else if (textAfterStart[i] == '}') { + numOpenBraces--; + } + + if (numOpenBraces == 0) { + var end = startIndex + i + 1; + string text = code.Substring(startIndex, end - startIndex); + structs.Add(text); + break; + } + } + } + + return structs.ToArray(); + } +} \ No newline at end of file diff --git a/src/lib-cpp/type-generator/StructParser.cs b/src/lib-cpp/type-generator/StructParser.cs new file mode 100644 index 00000000..d759a750 --- /dev/null +++ b/src/lib-cpp/type-generator/StructParser.cs @@ -0,0 +1,171 @@ +using System.Text.RegularExpressions; +using Superpower; +using Superpower.Parsers; +using Superpower.Model; +using Superpower.Tokenizers; + +public class RustField +{ + public string DocComment { get; set; } + public string Name { get; set; } + public string Type { get; set; } + public bool Optional { get; set; } +} + +public class RustStruct +{ + public string DocComment { get; set; } + public string Name { get; set; } + public List Fields { get; set; } +} + +public enum RustToken +{ + DocComment, + Attribute, + KeywordPub, + KeywordStruct, + KeywordImpl, + Identifier, + OpenBrace, // { + CloseBrace, // } + Colon, // : + Semicolon, // ; + Comma, // , + OtherSymbol, +} + +public static class StructParser +{ + // Tokenizer + private static readonly Tokenizer Tokenizer = new TokenizerBuilder() + .Ignore(Span.WhiteSpace) + .Ignore(Span.Regex(@"/\*[\s\S]*?\*/")) // Ignore multi-line comments + .Ignore(Span.Regex(@"(\s|^)\/\/[^\/].*")) // Ignore single-line comments but not doc comments + .Match(Span.Regex(@"///.*"), RustToken.DocComment) + .Match(Span.Regex(@"#\[[^\]]*\]"), RustToken.Attribute) + .Match(Span.EqualTo("pub"), RustToken.KeywordPub) + .Match(Span.EqualTo("struct"), RustToken.KeywordStruct) + .Match(Span.EqualTo("impl"), RustToken.KeywordImpl) + .Match(Span.Regex(@"[a-zA-Z_][a-zA-Z0-9_<>]*"), RustToken.Identifier) + .Match(Character.EqualTo('{'), RustToken.OpenBrace) + .Match(Character.EqualTo('}'), RustToken.CloseBrace) + .Match(Character.EqualTo(':'), RustToken.Colon) + .Match(Character.EqualTo(';'), RustToken.Semicolon) + .Match(Character.EqualTo(','), RustToken.Comma) + .Match(Character.AnyChar, RustToken.OtherSymbol) + .Ignore(Span.WhiteSpace) + .Build(); + + // Parsers + private static readonly TokenListParser DocComment = + Token.EqualTo(RustToken.DocComment) + .Many() + .Select(docs => string.Join("\n", docs.Select(doc => + doc.ToStringValue().Substring(3).Trim()))); + + private static readonly TokenListParser Attribute = + Token.EqualTo(RustToken.Attribute).Many().Select(_ => Unit.Value); + + private static TokenListParser SkipNestedBraces() + { + return + from open in Token.EqualTo(RustToken.OpenBrace) + from content in SkipNestedBracesContent() + from close in Token.EqualTo(RustToken.CloseBrace) + select Unit.Value; + } + + private static TokenListParser SkipNestedBracesContent() + { + return + (from nested in SkipNestedBraces() select Unit.Value) // handle recursive braces + .Or(from nonBrace in Token.Matching(kind => kind != RustToken.OpenBrace && kind != RustToken.CloseBrace,"non-brace").AtLeastOnce() select Unit.Value).Many() + .Select(_ => Unit.Value); + } + + private static readonly TokenListParser ImplBlock = + from implKeyword in Token.EqualTo(RustToken.KeywordImpl) + from rest in Token.Matching(kind => kind != RustToken.OpenBrace, "Expected tokens before '{'").AtLeastOnce() + from content in SkipNestedBraces() + select Unit.Value; + + private static readonly TokenListParser TypeParser = + from rest in Token.Matching(kind => kind != RustToken.Comma, "Expected tokens before ','").AtLeastOnce() + from end in Token.EqualTo(RustToken.Comma) + select string.Join(" ", rest.Select(t => t.ToStringValue())); + + private static readonly TokenListParser FieldDefinition = + from attrs1 in Attribute.Optional() + from docComments in DocComment.OptionalOrDefault() + from attrs2 in Attribute.Optional() + from pub in Token.EqualTo(RustToken.KeywordPub).Optional() + from fieldName in Token.EqualTo(RustToken.Identifier).Select(t => t.ToStringValue()) + from colon in Token.EqualTo(RustToken.Colon) + from fieldType in TypeParser + select new RustField + { + DocComment = docComments, + Name = fieldName, + Type = fieldType.Trim() + }; + + private static readonly TokenListParser> StructBody = + from openBrace in Token.EqualTo(RustToken.OpenBrace) + from fields in FieldDefinition.Many() + from closeBrace in Token.EqualTo(RustToken.CloseBrace) + select fields.ToList(); + + private static readonly TokenListParser StructDefinition = + from attrs1 in Attribute.Optional() + from docComments in DocComment.OptionalOrDefault() + from attrs2 in Attribute.Optional() + from pub in Token.EqualTo(RustToken.KeywordPub).Optional() + from structKeyword in Token.EqualTo(RustToken.KeywordStruct) + from structName in Token.EqualTo(RustToken.Identifier).Select(t => t.ToStringValue()) + from structBody in StructBody + select new RustStruct + { + DocComment = docComments, + Name = structName, + Fields = structBody + }; + + private static readonly TokenListParser TopLevelItem = + (from impl in ImplBlock + select (RustStruct)null) + .Or( + from structDef in StructDefinition + select structDef + ); + + public static IEnumerable ParseStructs(string code) + { + var tokens = Tokenizer.Tokenize(code); + var parser = TopLevelItem.Many(); + var result = parser(tokens); + + if (!result.HasValue) + { + throw new Exception(result.ToString()); + } + + var structs = result.Value.Where(s => s != null).ToArray(); + + foreach(var s in structs) + { + foreach(var f in s.Fields) + { + var match = Regex.Match(f.Type, @"Option<(.*)>"); + // If the field type is an Option, extract the inner type and set Optional to true + if (match.Success) + { + f.Type = match.Groups[1].Value; + f.Optional = true; + } + } + } + + return structs; + } +} diff --git a/src/lib-cpp/type-generator/Templates.cs b/src/lib-cpp/type-generator/Templates.cs new file mode 100644 index 00000000..88470a10 --- /dev/null +++ b/src/lib-cpp/type-generator/Templates.cs @@ -0,0 +1,294 @@ +using System.Text; + +public static class Templates +{ + private static string GetBasicCType(Dictionary nameMap, string rustType) + { + switch (rustType) { + case "PathBuf": + case "String": + return "char*"; + case "bool": + return "bool"; + case "i32": + return "int64_t"; + case "i64": + return "int64_t"; + case "u32": + return "uint32_t"; + case "u64": + return "uint64_t"; + default: + if (nameMap.TryGetValue(rustType, out var type)) { + return type; + } + + throw new NotSupportedException("Unsupported type for basic-c: " + rustType); + } + } + + private static string GetCPlusPlusType(string[] coreTypes, string rustType, bool optional) + { + string type = rustType switch { + "PathBuf" => "std::string", + "String" => "std::string", + "bool" => "bool", + "i32" => "int64_t", + "i64" => "int64_t", + "u32" => "uint32_t", + "u64" => "uint64_t", + _ => coreTypes.Contains(rustType) ? rustType : throw new NotSupportedException("Unsupported type for c-plus-plus: " + rustType), + }; + + return optional ? "std::optional<" + type + ">" : type; + } + + public static void WriteCBridgeMapping(Dictionary nameMap, IndentStringBuilder sb, RustStruct rs) + { + var cName = nameMap[rs.Name]; + + sb.AppendLine($"static inline {rs.Name}Dto to_bridge({cName}* pDto) {{"); + using (sb.Indent()) { + sb.AppendLine($"if (pDto == nullptr) {{ return {{}}; }}"); + sb.AppendLine($"return {{"); + using (sb.Indent()) { + foreach (var field in rs.Fields) { + string suffix = field.Optional ? "_opt" : ""; + string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower(); + if (nameMap.ContainsKey(field.Type)) { + sb.AppendLine($"to_bridge{suffix}(&pDto->{field.Name}),"); + } else if (type == "string") { + sb.AppendLine($"to_bridge{type}{suffix}(pDto->{field.Name}),"); + } else { + sb.AppendLine($"pDto->{field.Name},"); + } + } + } + + sb.AppendLine("};"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"static inline {rs.Name}DtoOption to_bridge_opt({cName}* pDto) {{"); + using (sb.Indent()) { + sb.AppendLine($"{rs.Name}DtoOption opt;"); + sb.AppendLine($"if (pDto == nullptr) {{"); + using (sb.Indent()) { + sb.AppendLine($"opt.has_data = false;"); + sb.AppendLine($"return opt;"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + sb.AppendLine($"opt.has_data = true;"); + sb.AppendLine($"opt.data = to_bridge(pDto);"); + sb.AppendLine($"return opt;"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"static inline void allocate_{rs.Name.ToLower()}({rs.Name}Dto bridgeDto, {cName}* pDto) {{"); + using (sb.Indent()) { + sb.AppendLine($"if (pDto == nullptr) {{ return; }}"); + foreach (var field in rs.Fields) { + string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower(); + string suffix = field.Optional ? "_opt" : ""; + sb.AppendLine( + nameMap.ContainsKey(field.Type) || type == "string" + ? $"allocate_{type}{suffix}(bridgeDto.{field.Name}, &pDto->{field.Name});" + : $"pDto->{field.Name} = bridgeDto.{field.Name};"); + } + } + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"static inline void free_{rs.Name.ToLower()}({cName}* pDto) {{"); + using (sb.Indent()) { + sb.AppendLine($"if (pDto == nullptr) {{ return; }}"); + foreach (var field in rs.Fields) { + string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower(); + if (nameMap.ContainsKey(field.Type)) { + sb.AppendLine($"free_{type}(&pDto->{field.Name});"); + } else if (type == "string") { + sb.AppendLine($"free(pDto->{field.Name});"); + } + } + } + sb.AppendLine($"}}"); + sb.AppendLine(); + } + + public static void WriteBasicC(Dictionary nameMap, IndentStringBuilder sb, RustStruct rs) + { + sb.AppendLine($"typedef struct {{"); + foreach (var field in rs.Fields) { + sb.AppendLine($" {GetBasicCType(nameMap, field.Type)} {field.Name};"); + } + + sb.AppendLine($"}} {nameMap[rs.Name]};"); + sb.AppendLine(); + } + + public static void WriteCPlusPlus(Dictionary nameMap, IndentStringBuilder sb, RustStruct rs) + { + var coreTypes = nameMap.Keys.ToArray(); + sb.AppendLine($"struct {rs.Name} {{"); + foreach (var field in rs.Fields) { + sb.AppendLine($" {GetCPlusPlusType(coreTypes, field.Type, field.Optional)} {field.Name};"); + } + + sb.AppendLine($"}};"); + sb.AppendLine(); + + sb.AppendLine($"static inline {nameMap[rs.Name]} to_c(const {rs.Name}& dto) {{"); + using (sb.Indent()) { + sb.AppendLine("return {"); + using (sb.Indent()) { + foreach (var field in rs.Fields) { + string suffix = field.Optional ? "_opt" : ""; + string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower(); + sb.AppendLine( + nameMap.ContainsKey(field.Type) + ? $"to_c{suffix}(dto.{field.Name})," + : $"to_c{type}{suffix}(dto.{field.Name}),"); + } + } + + sb.AppendLine("};"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"static inline {rs.Name} to_cpp(const {nameMap[rs.Name]}& dto) {{"); + using (sb.Indent()) { + sb.AppendLine("return {"); + using (sb.Indent()) { + foreach (var field in rs.Fields) { + string suffix = field.Optional ? "_opt" : ""; + string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower(); + sb.AppendLine( + nameMap.ContainsKey(field.Type) + ? $"to_cpp{suffix}(dto.{field.Name})," + : $"to_cpp{type}{suffix}(dto.{field.Name}),"); + } + } + + sb.AppendLine("};"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + } + + public static void WriteBridgeDto(string[] coreTypes, IndentStringBuilder sb, RustStruct rs) + { + Func nameMapper = (str) => + coreTypes.Contains(str) ? str + "Dto" : str; + + using (sb.Indent()) { + sb.AppendLine($"#[derive(Default)]"); + sb.AppendLine($"pub struct {nameMapper(rs.Name)} {{"); + foreach (var field in rs.Fields) { + string type = field.Type; + if (type == "PathBuf") { + type = "String"; + } + + using (sb.Indent()) { + if (field.Optional) { + sb.AppendLine($"pub {field.Name}: {nameMapper(type)}Option,"); + } else { + sb.AppendLine($"pub {field.Name}: {nameMapper(type)},"); + } + } + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"#[derive(Default)]"); + sb.AppendLine($"pub struct {nameMapper(rs.Name)}Option {{"); + using (sb.Indent()) { + sb.AppendLine($"pub data: {nameMapper(rs.Name)},"); + sb.AppendLine($"pub has_data: bool,"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + } + } + + public static void WriteBridgeToCoreMapping(string[] coreTypes, IndentStringBuilder sb, RustStruct rs) + { + Func nameMapper = (str) => coreTypes.Contains(str) ? str + "Dto" : str; + + sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_core(dto: &{nameMapper(rs.Name)}) -> {rs.Name} {{"); + ; + using (sb.Indent()) { + sb.AppendLine($"{rs.Name} {{"); + foreach (var field in rs.Fields) { + using (sb.Indent()) { + if (field.Optional) { + sb.AppendLine( + $"{field.Name}: if dto.{field.Name}.has_data {{ Some({field.Type.ToLower()}_to_core(&dto.{field.Name}.data)) }} else {{ None }},"); + } else { + sb.AppendLine($"{field.Name}: {field.Type.ToLower()}_to_core(&dto.{field.Name}),"); + } + } + } + + sb.AppendLine($"}}"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_bridge(dto: &{rs.Name}) -> {nameMapper(rs.Name)} {{"); + using (sb.Indent()) { + sb.AppendLine($"{nameMapper(rs.Name)} {{"); + foreach (var field in rs.Fields) { + using (sb.Indent()) { + if (field.Optional) { + sb.AppendLine( + $"{field.Name}: {nameMapper(field.Type)}Option {{ data: {field.Type.ToLower()}_to_bridge(&dto.{field.Name}.clone().unwrap_or_default()), has_data: dto.{field.Name}.is_some() }},"); + } else { + sb.AppendLine($"{field.Name}: {field.Type.ToLower()}_to_bridge(&dto.{field.Name}),"); + } + } + } + + sb.AppendLine($"}}"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_core_option(dto: &{nameMapper(rs.Name)}Option) -> Option<{rs.Name}> {{"); + ; + using (sb.Indent()) { + sb.AppendLine($"if dto.has_data {{ Some({rs.Name.ToLower()}_to_core(&dto.data)) }} else {{ None }}"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + + sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_bridge_option(dto: &Option<{rs.Name}>) -> {nameMapper(rs.Name)}Option {{"); + ; + using (sb.Indent()) { + sb.AppendLine($"match dto {{"); + using (sb.Indent()) { + sb.AppendLine($"Some(dto) => {nameMapper(rs.Name)}Option {{ data: {rs.Name.ToLower()}_to_bridge(dto), has_data: true }},"); + sb.AppendLine($"None => {nameMapper(rs.Name)}Option {{ data: Default::default(), has_data: false }},"); + } + + sb.AppendLine($"}}"); + } + + sb.AppendLine($"}}"); + sb.AppendLine(); + } +} \ No newline at end of file diff --git a/src/lib-cpp/type-generator/Util.cs b/src/lib-cpp/type-generator/Util.cs new file mode 100644 index 00000000..2cc54028 --- /dev/null +++ b/src/lib-cpp/type-generator/Util.cs @@ -0,0 +1,27 @@ +public static class Util +{ + public static void ReplaceTextInFile(string path, string placeholderName, string text) + { + var body = File.ReadAllText(path); + ReplaceTextBetween(ref body, placeholderName, text); + File.WriteAllText(path, body); + } + + public static void ReplaceTextBetween(ref string body, string placeholderName, string text) + { + var start = $"// !! AUTO-GENERATED-START {placeholderName}"; + var end = $"// !! AUTO-GENERATED-END {placeholderName}"; + var startIndex = body.IndexOf(start); + var endIndex = body.IndexOf(end); + if (startIndex == -1 || endIndex == -1) { + throw new InvalidOperationException($"Could not find placeholder {placeholderName}"); + } + + // normalize the start index to the beginning of the next line and end index to the end of the previous line + startIndex = body.IndexOf('\n', startIndex) + 1; + endIndex = body.LastIndexOf('\n', endIndex); + + body = body.Remove(startIndex, endIndex - startIndex); + body = body.Insert(startIndex, text.TrimEnd()); + } +} \ No newline at end of file diff --git a/src/lib-cpp/type-generator/type-generator.csproj b/src/lib-cpp/type-generator/type-generator.csproj new file mode 100644 index 00000000..0520d6a5 --- /dev/null +++ b/src/lib-cpp/type-generator/type-generator.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + <_Parameter1>SelfDir + <_Parameter2>$(MSBuildThisFileDirectory) + + + + diff --git a/src/lib-cpp/type-generator/type-generator.sln b/src/lib-cpp/type-generator/type-generator.sln new file mode 100644 index 00000000..e25e35b4 --- /dev/null +++ b/src/lib-cpp/type-generator/type-generator.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "type-generator", "type-generator.csproj", "{1C3B3A9D-0D1C-44BF-9004-FB973820932C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C3B3A9D-0D1C-44BF-9004-FB973820932C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C3B3A9D-0D1C-44BF-9004-FB973820932C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C3B3A9D-0D1C-44BF-9004-FB973820932C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C3B3A9D-0D1C-44BF-9004-FB973820932C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal