diff --git a/3rdparty/velopack/README.md b/3rdparty/velopack/README.md new file mode 100644 index 0000000000..ca63549f6d --- /dev/null +++ b/3rdparty/velopack/README.md @@ -0,0 +1,12 @@ +# Velopack libc(C/C++ runtime) + +来源:https://github.com/velopack/velopack GitHub release `1.2.0`,资产 `velopack_libc_1.2.0.zip`。 + +许可证:MIT(见上游仓库 LICENSE)。 + +当前仅 vendor Windows x64 资产: +- `include/Velopack.h`、`include/Velopack.hpp`:官方 C/C++ 头文件(C++ 为 C API 的薄封装)。 +- `lib/velopack_libc_win_x64_msvc.dll`:动态库本体。 +- `lib/velopack_libc_win_x64_msvc.dll.lib`:MSVC 导入库。 + +macOS / Linux / Windows arm64 等资产后续按平台补充(zip 内其余文件未 vendor)。 diff --git a/3rdparty/velopack/include/Velopack.h b/3rdparty/velopack/include/Velopack.h new file mode 100644 index 0000000000..112d92b97d --- /dev/null +++ b/3rdparty/velopack/include/Velopack.h @@ -0,0 +1,548 @@ +#ifndef VELOPACK_H +#define VELOPACK_H + +/* Generated with cbindgen:0.29.2 */ + +/* THIS FILE IS AUTO-GENERATED - DO NOT EDIT */ + +#include +#include +#include +#include + +/** + * The result of a call to check for updates. This can indicate that an update is available, or that an error occurred. + */ +enum vpkc_update_check_t +#ifdef __cplusplus + : int8_t +#endif // __cplusplus + { + UPDATE_ERROR = -1, + UPDATE_AVAILABLE = 0, + NO_UPDATE_AVAILABLE = 1, + REMOTE_IS_EMPTY = 2, +}; +#ifndef __cplusplus +typedef int8_t vpkc_update_check_t; +#endif // __cplusplus + +/** + * Opaque type for a Velopack UpdateSource. Must be freed with `vpkc_free_update_source`. + */ +typedef void vpkc_update_source_t; + +/** + * User delegate for to fetch a release feed. This function should return the raw JSON string of the release.json feed. + */ +typedef char *(*vpkc_release_feed_delegate_t)(void *p_user_data, const char *psz_releases_name); + +/** + * User delegate for freeing a release feed. This function should free the feed string returned by `vpkc_release_feed_delegate_t`. + */ +typedef void (*vpkc_free_release_feed_t)(void *p_user_data, char *psz_feed); + +/** + * An individual Velopack asset, could refer to an asset on-disk or in a remote package feed. + */ +typedef struct vpkc_asset_t { + /** + * The name or Id of the package containing this release. + */ + char *PackageId; + /** + * The version of this release. + */ + char *Version; + /** + * The type of asset (eg. "Full" or "Delta"). + */ + char *Type; + /** + * The filename of the update package containing this release. + */ + char *FileName; + /** + * The SHA1 checksum of the update package containing this release. + */ + char *SHA1; + /** + * The SHA256 checksum of the update package containing this release. + */ + char *SHA256; + /** + * The size in bytes of the update package containing this release. + */ + uint64_t Size; + /** + * The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string. + */ + char *NotesMarkdown; + /** + * The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string. + */ + char *NotesHtml; +} vpkc_asset_t; + +/** + * User delegate for downloading an asset file. This function is expected to download the provided asset + * to the provided local file path. Througout, you can use the progress callback to write progress reports. + * The function should return true if the download was successful, false otherwise. + * Progress + */ +typedef bool (*vpkc_download_asset_delegate_t)(void *p_user_data, + const struct vpkc_asset_t *p_asset, + const char *psz_local_path, + size_t progress_callback_id); + +/** + * Options to customise the behaviour of UpdateManager. + */ +typedef struct vpkc_update_options_t { + /** + * Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading). + * This could happen if a release has bugs and was retracted from the release feed, or if you're using + * ExplicitChannel to switch channels to another channel where the latest version on that + * channel is lower than the current version. + */ + bool AllowVersionDowngrade; + /** + * **This option should usually be left None**. + * Overrides the default channel used to fetch updates. + * The default channel will be whatever channel was specified on the command line when building this release. + * For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'. + * This allows users to automatically receive updates from the same channel they installed from. This options + * allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel + * without having to reinstall the application. + */ + char *ExplicitChannel; + /** + * Sets the maximum number of deltas to consider before falling back to a full update. + * The default is 10. Set to a negative number (eg. -1) to disable deltas. + */ + int32_t MaximumDeltasBeforeFallback; +} vpkc_update_options_t; + +/** + * VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth). + */ +typedef struct vpkc_locator_config_t { + /** + * The root directory of the current app, or the path to the AppImage file on Linux. + */ + char *RootAppDir; + /** + * The path to the Update.exe binary. + */ + char *UpdateExePath; + /** + * The path to the packages' directory. + */ + char *PackagesDir; + /** + * The current app manifest. + */ + char *ManifestPath; + /** + * The directory containing the application's user binaries. + */ + char *CurrentBinaryDir; + /** + * Whether the current application is portable or installed. + */ + bool IsPortable; +} vpkc_locator_config_t; + +/** + * Opaque type for the Velopack UpdateManager. Must be freed with `vpkc_free_update_manager`. + */ +typedef void vpkc_update_manager_t; + +/** + * Holds information about the current version and pending updates, such as how many there are, and access to release notes. + */ +typedef struct vpkc_update_info_t { + /** + * The available version that we are updating to. + */ + struct vpkc_asset_t *TargetFullRelease; + /** + * The base release that this update is based on. This is only available if the update is a delta update. + */ + struct vpkc_asset_t *BaseRelease; + /** + * The list of delta updates that can be applied to the base version to get to the target version. + */ + struct vpkc_asset_t **DeltasToTarget; + /** + * The number of elements in the DeltasToTarget array. + */ + size_t DeltasToTargetCount; + /** + * True if the update is a version downgrade or lateral move (such as when switching channels to the same version number). + * In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be + * deleted. + */ + bool IsDowngrade; +} vpkc_update_info_t; + +/** + * Progress callback function. + */ +typedef void (*vpkc_progress_callback_t)(void *p_user_data, size_t progress); + +/** + * VelopackApp startup hook callback function. + */ +typedef void (*vpkc_hook_callback_t)(void *p_user_data, const char *psz_app_version); + +/** + * Log callback function. + */ +typedef void (*vpkc_log_callback_t)(void *p_user_data, + const char *psz_level, + const char *psz_message); + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Create a new FileSource update source for a given file path. + * @param psz_file_path The path to a local directory containing updates. + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_file(const char *psz_file_path); + +/** + * Create a new HttpSource update source for a given HTTP URL. + * @param psz_http_url The URL to a remote update server. + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_http_url(const char *psz_http_url); + +/** + * Create a new GithubSource update source for a GitHub repository. + * @param psz_repo_url The GitHub repository URL (e.g. "https://github.com/user/repo"). + * @param psz_access_token Optional access token for private repositories (can be null). + * @param b_prerelease Whether to include pre-release versions. + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_github(const char *psz_repo_url, + const char *psz_access_token, + bool b_prerelease); + +/** + * Create a new GitlabSource update source for a GitLab repository. + * @param psz_repo_url The GitLab repository URL (e.g. "https://gitlab.com/user/repo"). + * @param psz_access_token Optional access token for private repositories (can be null). + * @param b_prerelease Whether to include pre-release versions. + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_gitlab(const char *psz_repo_url, + const char *psz_access_token, + bool b_prerelease); + +/** + * Create a new GiteaSource update source for a Gitea repository. + * @param psz_repo_url The Gitea repository URL (e.g. "https://gitea.example.com/user/repo"). + * @param psz_access_token Optional access token for private repositories (can be null). + * @param b_prerelease Whether to include pre-release versions. + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_gitea(const char *psz_repo_url, + const char *psz_access_token, + bool b_prerelease); + +/** + * Create a new VelopackFlowSource update source for Velopack Flow. + * @param psz_base_uri Optional base URI for the Velopack Flow API (can be null for default). + * @returns A new vpkc_update_source_t instance, or null on error. + */ +vpkc_update_source_t *vpkc_new_source_velopack_flow(const char *psz_base_uri); + +/** + * Create a new _CUSTOM_ update source with user-provided callbacks to fetch release feeds and download assets. + * You can report download progress using `vpkc_source_report_progress`. Note that the callbacks must be valid + * for the lifetime of any UpdateManager's that use this source. You should call `vpkc_free_source` to free the source, + * but note that if the source is still in use by an UpdateManager, it will not be freed until the UpdateManager is freed. + * Therefore to avoid possible issues, it is recommended to create this type of source once for the lifetime of your application. + * @param cb_release_feed A callback to fetch the release feed. + * @param cb_free_release_feed A callback to free the memory allocated by `cb_release_feed`. + * @param cb_download_entry A callback to download an asset. + * @param p_user_data Optional user data to be passed to the callbacks. + * @returns A new vpkc_update_source_t instance, or null on error. If null, the error will be available via `vpkc_get_last_error`. + */ +vpkc_update_source_t *vpkc_new_source_custom_callback(vpkc_release_feed_delegate_t cb_release_feed, + vpkc_free_release_feed_t cb_free_release_feed, + vpkc_download_asset_delegate_t cb_download_entry, + void *p_user_data); + +/** + * Sends a progress update to the callback with the specified ID. This is used by custom + * update sources created with `vpkc_new_source_custom_callback` to report download progress. + * @param progress_callback_id The ID of the progress callback to send the update to. + * @param progress The progress value to send (0-100). + */ +void vpkc_source_report_progress(size_t progress_callback_id, int16_t progress); + +/** + * Frees a vpkc_update_source_t instance. + * @param p_source The source to free. + */ +void vpkc_free_source(vpkc_update_source_t *p_source); + +/** + * Create a new UpdateManager instance. + * @param psz_url_or_path Location of the http update server url or path to the local update directory. + * @param p_options Optional extra configuration for update manager. + * @param p_locator Optional explicit path configuration for Velopack. If null, the default locator will be used. + * @param p_manager A pointer to where the new vpkc_update_manager_t* instance will be stored. + * @returns True if the update manager was created successfully, false otherwise. If false, the error will be available via `vpkc_get_last_error`. + */ +bool vpkc_new_update_manager(const char *psz_url_or_path, + struct vpkc_update_options_t *p_options, + struct vpkc_locator_config_t *p_locator, + vpkc_update_manager_t **p_manager); + +/** + * Create a new UpdateManager instance with a custom UpdateSource. + * @param p_source A pointer to a custom UpdateSource. + * @param p_options Optional extra configuration for update manager. + * @param p_locator Optional explicit path configuration for Velopack. If null, the default locator will be used. + * @param p_manager A pointer to where the new vpkc_update_manager_t* instance will be stored. + * @returns True if the update manager was created successfully, false otherwise. If false, the error will be available via `vpkc_get_last_error`. + */ +bool vpkc_new_update_manager_with_source(vpkc_update_source_t *p_source, + struct vpkc_update_options_t *p_options, + struct vpkc_locator_config_t *p_locator, + vpkc_update_manager_t **p_manager); + +/** + * Returns the currently installed version of the app. + * @param p_manager The update manager instance. + * @param psz_version A buffer to store the version string. + * @param c_version The size of the `psz_version` buffer. + * @returns The number of characters written to `psz_version` (including null terminator), or the required buffer size if the buffer is too small. + */ +size_t vpkc_get_current_version(vpkc_update_manager_t *p_manager, + char *psz_version, + size_t c_version); + +/** + * Returns the currently installed app id. + * @param p_manager The update manager instance. + * @param psz_id A buffer to store the app id string. + * @param c_id The size of the `psz_id` buffer. + * @returns The number of characters written to `psz_id` (including null terminator), or the required buffer size if the buffer is too small. + */ +size_t vpkc_get_app_id(vpkc_update_manager_t *p_manager, + char *psz_id, + size_t c_id); + +/** + * Returns whether the app is in portable mode. On Windows this can be true or false. + * On MacOS and Linux this will always be true. + * @param p_manager The update manager instance. + * @returns True if the app is in portable mode, false otherwise. + */ +bool vpkc_is_portable(vpkc_update_manager_t *p_manager); + +/** + * Returns an asset if there is an update downloaded which still needs to be applied. + * You can pass this asset to `vpkc_wait_exit_then_apply_updates` to apply the update. + * @param p_manager The update manager instance. + * @param p_asset A pointer to where the new vpkc_asset_t* instance will be stored. + * @returns True if there is an update pending restart, false otherwise. + */ +bool vpkc_update_pending_restart(vpkc_update_manager_t *p_manager, struct vpkc_asset_t **p_asset); + +/** + * Checks for updates. If there are updates available, this method will return an + * UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available. + * @param p_manager The update manager instance. + * @param p_update A pointer to where the new vpkc_update_info_t* instance will be stored if an update is available. + * @returns A `vpkc_update_check_t` value indicating the result of the check. If an update is available, the value will be `HasUpdate` and `p_update` will be populated. + */ +vpkc_update_check_t vpkc_check_for_updates(vpkc_update_manager_t *p_manager, + struct vpkc_update_info_t **p_update); + +/** + * Downloads the specified updates to the local app packages directory. Progress is reported back to the caller via an optional callback. + * This function will acquire a global update lock so may fail if there is already another update operation in progress. + * - If the update contains delta packages and the delta feature is enabled + * this method will attempt to unpack and prepare them. + * - If there is no delta update available, or there is an error preparing delta + * packages, this method will fall back to downloading the full version of the update. + * @param p_manager The update manager instance. + * @param p_update The update info object from `vpkc_check_for_updates`. + * @param cb_progress An optional callback to report download progress (0-100). + * @param p_user_data Optional user data to be passed to the progress callback. + * @returns true on success, false on failure. If false, the error will be available via `vpkc_get_last_error`. + */ +bool vpkc_download_updates(vpkc_update_manager_t *p_manager, + struct vpkc_update_info_t *p_update, + vpkc_progress_callback_t cb_progress, + void *p_user_data); + +/** + * This will launch the Velopack updater and tell it to wait for this program to exit gracefully. + * You should then clean up any state and exit your app. The updater will apply updates and then + * (if specified) restart your app. The updater will only wait for 60 seconds before giving up. + * @param p_manager The update manager instance. + * @param p_asset The asset to apply. This can be from `vpkc_update_pending_restart` or `vpkc_update_info_get_target_asset`. + * @param b_silent True to attempt to apply the update without showing any UI. + * @param b_restart True to restart the app after the update is applied. + * @param p_restart_args An array of command line arguments to pass to the new process when it's restarted. + * @param c_restart_args The number of arguments in `p_restart_args`. + * @returns true on success, false on failure. If false, the error will be available via `vpkc_get_last_error`. + */ +bool vpkc_wait_exit_then_apply_updates(vpkc_update_manager_t *p_manager, + struct vpkc_asset_t *p_asset, + bool b_silent, + bool b_restart, + char **p_restart_args, + size_t c_restart_args); + +/** + * This will launch the Velopack updater and optionally wait for a program to exit gracefully. + * This method is unsafe because it does not necessarily wait for any / the correct process to exit + * before applying updates. The `vpkc_wait_exit_then_apply_updates` method is recommended for most use cases. + * If dw_wait_pid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended). + * @param p_manager The update manager instance. + * @param p_asset The asset to apply. This can be from `vpkc_update_pending_restart` or `vpkc_update_info_get_target_asset`. + * @param b_silent True to attempt to apply the update without showing any UI. + * @param dw_wait_pid The process ID to wait for before applying updates. If 0, the updater will not wait. + * @param b_restart True to restart the app after the update is applied. + * @param p_restart_args An array of command line arguments to pass to the new process when it's restarted. + * @param c_restart_args The number of arguments in `p_restart_args`. + * @returns true on success, false on failure. If false, the error will be available via `vpkc_get_last_error`. + */ +bool vpkc_unsafe_apply_updates(vpkc_update_manager_t *p_manager, + struct vpkc_asset_t *p_asset, + bool b_silent, + uint32_t dw_wait_pid, + bool b_restart, + char **p_restart_args, + size_t c_restart_args); + +/** + * Frees a vpkc_update_manager_t instance. + * @param p_manager The update manager instance to free. + */ +void vpkc_free_update_manager(vpkc_update_manager_t *p_manager); + +/** + * Frees a vpkc_update_info_t instance. + * @param p_update_info The update info instance to free. + */ +void vpkc_free_update_info(struct vpkc_update_info_t *p_update_info); + +/** + * Frees a vpkc_asset_t instance. + * @param p_asset The asset instance to free. + */ +void vpkc_free_asset(struct vpkc_asset_t *p_asset); + +/** + * VelopackApp helps you to handle app activation events correctly. + * This should be used as early as possible in your application startup code. + * (eg. the beginning of main() or wherever your entry point is). + * This function will not return in some cases. + * @param p_user_data Optional user data to be passed to the callbacks. + */ +void vpkc_app_run(void *p_user_data); + +/** + * Set whether to automatically apply downloaded updates on startup. This is ON by default. + * @param b_auto_apply True to automatically apply updates, false otherwise. + */ +void vpkc_app_set_auto_apply_on_startup(bool b_auto_apply); + +/** + * Override the command line arguments used by VelopackApp. (by default this is env::args().skip(1)) + * @param p_args An array of command line arguments. + * @param c_args The number of arguments in `p_args`. + */ +void vpkc_app_set_args(char **p_args, size_t c_args); + +/** + * VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth). + * @param p_locator The locator configuration to use. + */ +void vpkc_app_set_locator(struct vpkc_locator_config_t *p_locator); + +/** + * Sets a callback to be run after the app is installed. + * WARNING: FastCallback hooks are run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cb_after_install The callback to run after the app is installed. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_after_install(vpkc_hook_callback_t cb_after_install); + +/** + * Sets a callback to be run before the app is uninstalled. + * WARNING: FastCallback hooks are run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cb_before_uninstall The callback to run before the app is uninstalled. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_before_uninstall(vpkc_hook_callback_t cb_before_uninstall); + +/** + * Sets a callback to be run before the app is updated. + * WARNING: FastCallback hooks are run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cb_before_update The callback to run before the app is updated. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_before_update(vpkc_hook_callback_t cb_before_update); + +/** + * Sets a callback to be run after the app is updated. + * WARNING: FastCallback hooks are run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cb_after_update The callback to run after the app is updated. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_after_update(vpkc_hook_callback_t cb_after_update); + +/** + * This hook is triggered when the application is started for the first time after installation. + * @param cb_first_run The callback to run on first run. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_first_run(vpkc_hook_callback_t cb_first_run); + +/** + * This hook is triggered when the application is restarted by Velopack after installing updates. + * @param cb_restarted The callback to run after the app is restarted. The callback takes a user data pointer and the version of the app as a string. + */ +void vpkc_app_set_hook_restarted(vpkc_hook_callback_t cb_restarted); + +/** + * Get the last error message that occurred in the Velopack library. + * @param psz_error A buffer to store the error message. + * @param c_error The size of the `psz_error` buffer. + * @returns The number of characters written to `psz_error` (including null terminator). If the return value is greater than `c_error`, the buffer was too small and the message was truncated. + */ +size_t vpkc_get_last_error(char *psz_error, + size_t c_error); + +/** + * Set a custom log callback. This will be called for all log messages generated by the Velopack library. + * @param cb_log The callback to call with log messages. The callback takes a user data pointer, a log level, and the log message as a string. + * @param p_user_data Optional user data to be passed to the callback. + */ +void vpkc_set_logger(vpkc_log_callback_t cb_log, + void *p_user_data); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* VELOPACK_H */ diff --git a/3rdparty/velopack/include/Velopack.hpp b/3rdparty/velopack/include/Velopack.hpp new file mode 100644 index 0000000000..c95fcb36ed --- /dev/null +++ b/3rdparty/velopack/include/Velopack.hpp @@ -0,0 +1,978 @@ +//! This header provides the C++ API for the Velopack library. +//! This C++ API is a thin wrapper around the C API, providing a more idiomatic C++ interface. +#ifndef VELOPACK_HPP +#define VELOPACK_HPP + +#include +#include +#include +#include +#include +#include + +#include "Velopack.h" + +#if !defined(_WIN32) +#include +#endif + +namespace Velopack { + +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::optional to_cpp_string(const char* psz) +{ + return psz == nullptr ? std::optional("") : std::optional(psz); +} + +static inline char* alloc_c_string(const std::string& str) +{ + char* result = new char[str.size() + 1]; // +1 for null-terminator +#ifdef _WIN32 + strcpy_s(result, str.size() + 1, str.c_str()); // Copy string content +#else + strcpy(result, str.c_str()); // Copy string content +#endif + result[str.size()] = '\0'; // Null-terminate the string + return result; +} + +static inline char* alloc_c_string(const std::optional& str) +{ + if (!str.has_value()) { return nullptr; } + return alloc_c_string(str.value()); +} + +static inline void free_c_string(char* str) +{ + delete[] str; +} + +static inline char** alloc_c_string_vec(const std::vector& dto, size_t* count) +{ + if (dto.empty()) { + *count = 0; + return nullptr; + } + *count = dto.size(); + char** arr = new char* [*count]; + for (size_t i = 0; i < *count; ++i) { + arr[i] = alloc_c_string(dto[i]); + } + return arr; +} + +static inline void free_c_string_vec(char** arr, size_t size) +{ + for (size_t i = 0; i < size; ++i) { + free_c_string(arr[i]); + arr[i] = nullptr; + } + delete[] arr; +} + +template +inline T unwrap(const std::optional& opt, const std::string& message = "Expected value not present") { + if (!opt.has_value()) { + throw std::runtime_error(message); + } + return opt.value(); +} + +// !! AUTO-GENERATED-START CPP_TYPES + +/** VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth). */ +struct VelopackLocatorConfig { + /** The root directory of the current app, or the path to the AppImage file on Linux. */ + std::string RootAppDir; + /** The path to the Update.exe binary. */ + std::string UpdateExePath; + /** The path to the packages' directory. */ + std::string PackagesDir; + /** The current app manifest. */ + std::string ManifestPath; + /** The directory containing the application's user binaries. */ + std::string CurrentBinaryDir; + /** Whether the current application is portable or installed. */ + bool IsPortable; +}; + +static inline std::optional to_cpp_VelopackLocatorConfig(const vpkc_locator_config_t* dto) { + if (dto == nullptr) { return std::nullopt; } + return std::optional({ + unwrap(to_cpp_string(dto->RootAppDir), "Required property RootAppDir was null"), + unwrap(to_cpp_string(dto->UpdateExePath), "Required property UpdateExePath was null"), + unwrap(to_cpp_string(dto->PackagesDir), "Required property PackagesDir was null"), + unwrap(to_cpp_string(dto->ManifestPath), "Required property ManifestPath was null"), + unwrap(to_cpp_string(dto->CurrentBinaryDir), "Required property CurrentBinaryDir was null"), + dto->IsPortable, + }); +} + +static inline std::vector to_cpp_VelopackLocatorConfig_vec(const vpkc_locator_config_t* const* arr, size_t c) { + if (arr == nullptr || c < 1) { return std::vector(); } + std::vector result; + result.reserve(c); + for (size_t i = 0; i < c; ++i) { + auto dto = arr[i]; + if (dto == nullptr) { continue; } + result.push_back(unwrap(to_cpp_VelopackLocatorConfig(dto))); + } + return result; +} + +static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig_ptr(const VelopackLocatorConfig* dto) { + if (dto == nullptr) { return nullptr; } + vpkc_locator_config_t* obj = new vpkc_locator_config_t{}; + obj->RootAppDir = alloc_c_string(dto->RootAppDir); + obj->UpdateExePath = alloc_c_string(dto->UpdateExePath); + obj->PackagesDir = alloc_c_string(dto->PackagesDir); + obj->ManifestPath = alloc_c_string(dto->ManifestPath); + obj->CurrentBinaryDir = alloc_c_string(dto->CurrentBinaryDir); + obj->IsPortable = dto->IsPortable; + return obj; +} + +static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig(const std::optional& dto) { + if (!dto.has_value()) { return nullptr; } + VelopackLocatorConfig obj = unwrap(dto); + return alloc_c_VelopackLocatorConfig_ptr(&obj); +} + +static inline vpkc_locator_config_t** alloc_c_VelopackLocatorConfig_vec(const std::vector& dto, size_t* count) { + if (dto.empty()) { + *count = 0; + return nullptr; + } + *count = dto.size(); + vpkc_locator_config_t** arr = new vpkc_locator_config_t*[*count]; + for (size_t i = 0; i < *count; ++i) { + arr[i] = alloc_c_VelopackLocatorConfig(dto[i]); + } + return arr; +} + +static inline void free_c_VelopackLocatorConfig(vpkc_locator_config_t* obj) { + if (obj == nullptr) { return; } + free_c_string(obj->RootAppDir); + free_c_string(obj->UpdateExePath); + free_c_string(obj->PackagesDir); + free_c_string(obj->ManifestPath); + free_c_string(obj->CurrentBinaryDir); + + delete obj; +} + +static inline void free_c_VelopackLocatorConfig_vec(vpkc_locator_config_t** arr, size_t count) { + if (arr == nullptr || count < 1) { return; } + for (size_t i = 0; i < count; ++i) { + free_c_VelopackLocatorConfig(arr[i]); + } + delete[] arr; +} + +/** An individual Velopack asset, could refer to an asset on-disk or in a remote package feed. */ +struct VelopackAsset { + /** The name or Id of the package containing this release. */ + std::string PackageId; + /** The version of this release. */ + std::string Version; + /** The type of asset (eg. "Full" or "Delta"). */ + std::string Type; + /** The filename of the update package containing this release. */ + std::string FileName; + /** The SHA1 checksum of the update package containing this release. */ + std::string SHA1; + /** The SHA256 checksum of the update package containing this release. */ + std::string SHA256; + /** The size in bytes of the update package containing this release. */ + uint64_t Size; + /** The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string. */ + std::string NotesMarkdown; + /** The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string. */ + std::string NotesHtml; +}; + +static inline std::optional to_cpp_VelopackAsset(const vpkc_asset_t* dto) { + if (dto == nullptr) { return std::nullopt; } + return std::optional({ + unwrap(to_cpp_string(dto->PackageId), "Required property PackageId was null"), + unwrap(to_cpp_string(dto->Version), "Required property Version was null"), + unwrap(to_cpp_string(dto->Type), "Required property Type was null"), + unwrap(to_cpp_string(dto->FileName), "Required property FileName was null"), + unwrap(to_cpp_string(dto->SHA1), "Required property SHA1 was null"), + unwrap(to_cpp_string(dto->SHA256), "Required property SHA256 was null"), + dto->Size, + unwrap(to_cpp_string(dto->NotesMarkdown), "Required property NotesMarkdown was null"), + unwrap(to_cpp_string(dto->NotesHtml), "Required property NotesHtml was null"), + }); +} + +static inline std::vector to_cpp_VelopackAsset_vec(const vpkc_asset_t* const* arr, size_t c) { + if (arr == nullptr || c < 1) { return std::vector(); } + std::vector result; + result.reserve(c); + for (size_t i = 0; i < c; ++i) { + auto dto = arr[i]; + if (dto == nullptr) { continue; } + result.push_back(unwrap(to_cpp_VelopackAsset(dto))); + } + return result; +} + +static inline vpkc_asset_t* alloc_c_VelopackAsset_ptr(const VelopackAsset* dto) { + if (dto == nullptr) { return nullptr; } + vpkc_asset_t* obj = new vpkc_asset_t{}; + obj->PackageId = alloc_c_string(dto->PackageId); + obj->Version = alloc_c_string(dto->Version); + obj->Type = alloc_c_string(dto->Type); + obj->FileName = alloc_c_string(dto->FileName); + obj->SHA1 = alloc_c_string(dto->SHA1); + obj->SHA256 = alloc_c_string(dto->SHA256); + obj->Size = dto->Size; + obj->NotesMarkdown = alloc_c_string(dto->NotesMarkdown); + obj->NotesHtml = alloc_c_string(dto->NotesHtml); + return obj; +} + +static inline vpkc_asset_t* alloc_c_VelopackAsset(const std::optional& dto) { + if (!dto.has_value()) { return nullptr; } + VelopackAsset obj = unwrap(dto); + return alloc_c_VelopackAsset_ptr(&obj); +} + +static inline vpkc_asset_t** alloc_c_VelopackAsset_vec(const std::vector& dto, size_t* count) { + if (dto.empty()) { + *count = 0; + return nullptr; + } + *count = dto.size(); + vpkc_asset_t** arr = new vpkc_asset_t*[*count]; + for (size_t i = 0; i < *count; ++i) { + arr[i] = alloc_c_VelopackAsset(dto[i]); + } + return arr; +} + +static inline void free_c_VelopackAsset(vpkc_asset_t* obj) { + if (obj == nullptr) { return; } + free_c_string(obj->PackageId); + free_c_string(obj->Version); + free_c_string(obj->Type); + free_c_string(obj->FileName); + free_c_string(obj->SHA1); + free_c_string(obj->SHA256); + + free_c_string(obj->NotesMarkdown); + free_c_string(obj->NotesHtml); + delete obj; +} + +static inline void free_c_VelopackAsset_vec(vpkc_asset_t** arr, size_t count) { + if (arr == nullptr || count < 1) { return; } + for (size_t i = 0; i < count; ++i) { + free_c_VelopackAsset(arr[i]); + } + delete[] arr; +} + +/** Holds information about the current version and pending updates, such as how many there are, and access to release notes. */ +struct UpdateInfo { + /** The available version that we are updating to. */ + VelopackAsset TargetFullRelease; + /** The base release that this update is based on. This is only available if the update is a delta update. */ + std::optional BaseRelease; + /** The list of delta updates that can be applied to the base version to get to the target version. */ + std::vector DeltasToTarget; + /** + * True if the update is a version downgrade or lateral move (such as when switching channels to the same version number). + * In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be + * deleted. + */ + bool IsDowngrade; +}; + +static inline std::optional to_cpp_UpdateInfo(const vpkc_update_info_t* dto) { + if (dto == nullptr) { return std::nullopt; } + return std::optional({ + unwrap(to_cpp_VelopackAsset(dto->TargetFullRelease), "Required property TargetFullRelease was null"), + to_cpp_VelopackAsset(dto->BaseRelease), + to_cpp_VelopackAsset_vec(dto->DeltasToTarget, dto->DeltasToTargetCount), + dto->IsDowngrade, + }); +} + +static inline std::vector to_cpp_UpdateInfo_vec(const vpkc_update_info_t* const* arr, size_t c) { + if (arr == nullptr || c < 1) { return std::vector(); } + std::vector result; + result.reserve(c); + for (size_t i = 0; i < c; ++i) { + auto dto = arr[i]; + if (dto == nullptr) { continue; } + result.push_back(unwrap(to_cpp_UpdateInfo(dto))); + } + return result; +} + +static inline vpkc_update_info_t* alloc_c_UpdateInfo_ptr(const UpdateInfo* dto) { + if (dto == nullptr) { return nullptr; } + vpkc_update_info_t* obj = new vpkc_update_info_t{}; + obj->TargetFullRelease = alloc_c_VelopackAsset(dto->TargetFullRelease); + obj->BaseRelease = alloc_c_VelopackAsset(dto->BaseRelease); + obj->DeltasToTarget = alloc_c_VelopackAsset_vec(dto->DeltasToTarget, &obj->DeltasToTargetCount); + obj->IsDowngrade = dto->IsDowngrade; + return obj; +} + +static inline vpkc_update_info_t* alloc_c_UpdateInfo(const std::optional& dto) { + if (!dto.has_value()) { return nullptr; } + UpdateInfo obj = unwrap(dto); + return alloc_c_UpdateInfo_ptr(&obj); +} + +static inline vpkc_update_info_t** alloc_c_UpdateInfo_vec(const std::vector& dto, size_t* count) { + if (dto.empty()) { + *count = 0; + return nullptr; + } + *count = dto.size(); + vpkc_update_info_t** arr = new vpkc_update_info_t*[*count]; + for (size_t i = 0; i < *count; ++i) { + arr[i] = alloc_c_UpdateInfo(dto[i]); + } + return arr; +} + +static inline void free_c_UpdateInfo(vpkc_update_info_t* obj) { + if (obj == nullptr) { return; } + free_c_VelopackAsset(obj->TargetFullRelease); + free_c_VelopackAsset(obj->BaseRelease); + free_c_VelopackAsset_vec(obj->DeltasToTarget, obj->DeltasToTargetCount); + + delete obj; +} + +static inline void free_c_UpdateInfo_vec(vpkc_update_info_t** arr, size_t count) { + if (arr == nullptr || count < 1) { return; } + for (size_t i = 0; i < count; ++i) { + free_c_UpdateInfo(arr[i]); + } + delete[] arr; +} + +/** Options to customise the behaviour of UpdateManager. */ +struct UpdateOptions { + /** + * Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading). + * This could happen if a release has bugs and was retracted from the release feed, or if you're using + * ExplicitChannel to switch channels to another channel where the latest version on that + * channel is lower than the current version. + */ + bool AllowVersionDowngrade; + /** + * **This option should usually be left None**. + * Overrides the default channel used to fetch updates. + * The default channel will be whatever channel was specified on the command line when building this release. + * For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'. + * This allows users to automatically receive updates from the same channel they installed from. This options + * allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel + * without having to reinstall the application. + */ + std::optional ExplicitChannel; + /** + * Sets the maximum number of deltas to consider before falling back to a full update. + * The default is 10. Set to a negative number (eg. -1) to disable deltas. + */ + int32_t MaximumDeltasBeforeFallback; +}; + +static inline std::optional to_cpp_UpdateOptions(const vpkc_update_options_t* dto) { + if (dto == nullptr) { return std::nullopt; } + return std::optional({ + dto->AllowVersionDowngrade, + to_cpp_string(dto->ExplicitChannel), + dto->MaximumDeltasBeforeFallback, + }); +} + +static inline std::vector to_cpp_UpdateOptions_vec(const vpkc_update_options_t* const* arr, size_t c) { + if (arr == nullptr || c < 1) { return std::vector(); } + std::vector result; + result.reserve(c); + for (size_t i = 0; i < c; ++i) { + auto dto = arr[i]; + if (dto == nullptr) { continue; } + result.push_back(unwrap(to_cpp_UpdateOptions(dto))); + } + return result; +} + +static inline vpkc_update_options_t* alloc_c_UpdateOptions_ptr(const UpdateOptions* dto) { + if (dto == nullptr) { return nullptr; } + vpkc_update_options_t* obj = new vpkc_update_options_t{}; + obj->AllowVersionDowngrade = dto->AllowVersionDowngrade; + obj->ExplicitChannel = alloc_c_string(dto->ExplicitChannel); + obj->MaximumDeltasBeforeFallback = dto->MaximumDeltasBeforeFallback; + return obj; +} + +static inline vpkc_update_options_t* alloc_c_UpdateOptions(const std::optional& dto) { + if (!dto.has_value()) { return nullptr; } + UpdateOptions obj = unwrap(dto); + return alloc_c_UpdateOptions_ptr(&obj); +} + +static inline vpkc_update_options_t** alloc_c_UpdateOptions_vec(const std::vector& dto, size_t* count) { + if (dto.empty()) { + *count = 0; + return nullptr; + } + *count = dto.size(); + vpkc_update_options_t** arr = new vpkc_update_options_t*[*count]; + for (size_t i = 0; i < *count; ++i) { + arr[i] = alloc_c_UpdateOptions(dto[i]); + } + return arr; +} + +static inline void free_c_UpdateOptions(vpkc_update_options_t* obj) { + if (obj == nullptr) { return; } + + free_c_string(obj->ExplicitChannel); + + delete obj; +} + +static inline void free_c_UpdateOptions_vec(vpkc_update_options_t** arr, size_t count) { + if (arr == nullptr || count < 1) { return; } + for (size_t i = 0; i < count; ++i) { + free_c_UpdateOptions(arr[i]); + } + delete[] arr; +} +// !! AUTO-GENERATED-END CPP_TYPES + +/** + * VelopackApp helps you to handle app activation events correctly. + * This should be used as early as possible in your application startup code. + * (eg. the beginning of main() or wherever your entry point is) + * To use this class, you should create a new VelopackApp::Build() builder instance, + * and then chain calls to the builder to configure your app. + * Finally, call the Run() method to execute the Velopack logic. + */ +class VelopackApp { +private: + VelopackApp() {}; +public: + /** + * Create and return a new VelopackApp builder. + */ + static VelopackApp Build() { + return VelopackApp(); + }; + + /** + * Attach a custom callback to receive log messages from Velopack. + */ + VelopackApp& SetLogger(vpkc_log_callback_t cbInstall, void* p_user_data) { + vpkc_set_logger(cbInstall, p_user_data); + return *this; + }; + + /** + * Set whether to automatically apply downloaded updates on startup. This is ON by default. + */ + VelopackApp& SetAutoApplyOnStartup(bool bAutoApply) { + vpkc_app_set_auto_apply_on_startup(bAutoApply); + return *this; + }; + + /** + * Override the command line arguments used by VelopackApp. + * By default, Velopack will use the command line arguments from the current process. + * @param args The command line arguments to use. + * @returns A reference to the builder. + */ + VelopackApp& SetArgs(const std::vector& args) { + size_t c; + char** pArgs = alloc_c_string_vec(args, &c); + vpkc_app_set_args(pArgs, c); + free_c_string_vec(pArgs, c); + return *this; + }; + + /** + * Override the default VelopackLocator. The locator is used to find important paths for the application. + * @param locator The locator to use. + * @returns A reference to the builder. + */ + VelopackApp& SetLocator(const VelopackLocatorConfig& locator) { + vpkc_locator_config_t* vpkc_locator = alloc_c_VelopackLocatorConfig(locator); + vpkc_app_set_locator(vpkc_locator); + free_c_VelopackLocatorConfig(vpkc_locator); + return *this; + }; + + /** + * This hook is triggered after the app has been installed. + * WARNING: This hook is run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cbAfterInstall The callback to run after the app has been installed. + * @returns A reference to the builder. + */ + VelopackApp& OnAfterInstall(vpkc_hook_callback_t cbAfterInstall) { + vpkc_app_set_hook_after_install(cbAfterInstall); + return *this; + }; + + /** + * This hook is triggered before the app is uninstalled. + * WARNING: This hook is run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cbBeforeUninstall The callback to run before the app is uninstalled. + * @returns A reference to the builder. + */ + VelopackApp& OnBeforeUninstall(vpkc_hook_callback_t cbBeforeUninstall) { + vpkc_app_set_hook_before_uninstall(cbBeforeUninstall); + return *this; + }; + + /** + * This hook is triggered before the app is updated. + * WARNING: This hook is run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cbBeforeUpdate The callback to run before the app is updated. + * @returns A reference to the builder. + */ + VelopackApp& OnBeforeUpdate(vpkc_hook_callback_t cbBeforeUpdate) { + vpkc_app_set_hook_before_update(cbBeforeUpdate); + return *this; + }; + + /** + * This hook is triggered after the app is updated. + * WARNING: This hook is run during critical stages of Velopack operations. + * Your code will be run and then the process will exit. + * If your code has not completed within 30 seconds, it will be terminated. + * Only supported on windows; On other operating systems, this will never be called. + * @param cbAfterUpdate The callback to run after the app is updated. + * @returns A reference to the builder. + */ + VelopackApp& OnAfterUpdate(vpkc_hook_callback_t cbAfterUpdate) { + vpkc_app_set_hook_after_update(cbAfterUpdate); + return *this; + }; + + /** + * This hook is triggered when the application is started for the first time after installation. + * @param cbFirstRun The callback to run when the application is started for the first time. + * @returns A reference to the builder. + */ + VelopackApp& OnFirstRun(vpkc_hook_callback_t cbFirstRun) { + vpkc_app_set_hook_first_run(cbFirstRun); + return *this; + }; + + /** + * This hook is triggered when the application is restarted by Velopack after installing updates. + * @param cbRestarted The callback to run when the application is restarted. + * @returns A reference to the builder. + */ + VelopackApp& OnRestarted(vpkc_hook_callback_t cbRestarted) { + vpkc_app_set_hook_restarted(cbRestarted); + return *this; + }; + + /** + * Runs the Velopack startup logic. This should be the first thing to run in your app. + * In some circumstances it may terminate/restart the process to perform tasks. + * @param pUserData A pointer to user data that will be passed to any hooks that are executed. + */ + void Run(void* pUserData = 0) { + vpkc_app_run(pUserData); + }; +}; + +/** + * Progress callback function. Call with values between 0 and 100 inclusive. + */ +typedef std::function vpkc_progress_send_t; + +/** + * Base class for all update sources. Holds a pointer to a Rust-side update source. + * All built-in sources (FileSource, HttpSource, GithubSource, etc.) inherit from this directly. + * For custom sources with user-provided callbacks, inherit from IUpdateSource instead. + */ +class IUpdateSourcePointer { + friend class UpdateManager; +protected: + vpkc_update_source_t* m_pSource = nullptr; + explicit IUpdateSourcePointer(vpkc_update_source_t* pSource) : m_pSource(pSource) { } +public: + virtual ~IUpdateSourcePointer() { + if (m_pSource) vpkc_free_source(m_pSource); + } + // Non-copyable, movable + IUpdateSourcePointer(const IUpdateSourcePointer&) = delete; + IUpdateSourcePointer& operator=(const IUpdateSourcePointer&) = delete; + IUpdateSourcePointer(IUpdateSourcePointer&& other) noexcept : m_pSource(other.m_pSource) { other.m_pSource = nullptr; } + IUpdateSourcePointer& operator=(IUpdateSourcePointer&& other) noexcept { + if (this != &other) { + if (m_pSource) vpkc_free_source(m_pSource); + m_pSource = other.m_pSource; + other.m_pSource = nullptr; + } + return *this; + } +}; + +/** + * Abstract class for custom update sources. Subclass this and implement GetReleaseFeed and + * DownloadReleaseEntry to provide a custom way of fetching releases and downloading assets. + * SAFETY: It is your responsibility to ensure that a derived class instance is thread-safe, + * as Velopack may call methods on this class from multiple threads. + */ +class IUpdateSource : public IUpdateSourcePointer { +public: + /** + * Destructor for IUpdateSource. + */ + virtual ~IUpdateSource() = default; + /** + * Default constructor for IUpdateSource. This will create a new custom source that calls back into the virtual methods of this class. + */ + IUpdateSource() : IUpdateSourcePointer(nullptr) { + m_pSource = vpkc_new_source_custom_callback( + [](void* userData, const char* releasesName) { + IUpdateSource* source = reinterpret_cast(userData); + std::string json = source->GetReleaseFeed(releasesName); + return alloc_c_string(json); + }, + [](void* userData, char* pszFeed) { + free_c_string(pszFeed); + }, + [](void* userData, const struct vpkc_asset_t *pAsset, const char* pszLocalPath, size_t progressCallbackId) { + IUpdateSource* source = reinterpret_cast(userData); + VelopackAsset asset = to_cpp_VelopackAsset(pAsset).value(); + std::string localPath = to_cpp_string(pszLocalPath).value(); + std::function progress_callback = [progressCallbackId](int16_t progress) { + vpkc_source_report_progress(progressCallbackId, progress); + }; + return source->DownloadReleaseEntry(asset, localPath, progress_callback); + }, + this); + if (!m_pSource) { + throw_last_error(); + } + } + + /** + * Fetches the release feed json for the specified releases name, and returns it as a string. + */ + virtual const std::string GetReleaseFeed(const std::string releasesName) = 0; + + /** + * Downloads an asset to the specified local file path. Progress is reported back to Velopack via a callback. + */ + virtual bool DownloadReleaseEntry(const VelopackAsset& asset, const std::string localFilePath, vpkc_progress_send_t progress) = 0; +}; + +/** + * A built-in update source that reads release feeds and downloads assets from a local file path. + */ +class FileSource : public IUpdateSourcePointer { +public: + /** + * Creates a new FileSource. + * @param filePath The path to the directory containing the releases. + */ + FileSource(const std::string& filePath) : IUpdateSourcePointer(vpkc_new_source_file(filePath.c_str())) { } +}; + +/** + * A built-in update source that reads release feeds and downloads assets from a remote HTTP URL. + */ +class HttpSource : public IUpdateSourcePointer { +public: + /** + * Creates a new HttpSource. + * @param httpUrl The URL to the releases feed. + */ + HttpSource(const std::string& httpUrl) : IUpdateSourcePointer(vpkc_new_source_http_url(httpUrl.c_str())) { } +}; + +/** + * A built-in update source that reads release feeds and downloads assets from a GitHub repository. + */ +class GithubSource : public IUpdateSourcePointer { +public: + /** + * Creates a new GithubSource. + * @param repoUrl The GitHub repository URL (e.g. "https://github.com/user/repo"). + * @param accessToken Optional access token for private repositories. + * @param prerelease Whether to include pre-release versions. + */ + GithubSource(const std::string& repoUrl, const std::string& accessToken = "", bool prerelease = false) + : IUpdateSourcePointer(vpkc_new_source_github(repoUrl.c_str(), accessToken.empty() ? nullptr : accessToken.c_str(), prerelease)) { } +}; + +/** + * A built-in update source that reads release feeds and downloads assets from a GitLab repository. + */ +class GitlabSource : public IUpdateSourcePointer { +public: + /** + * Creates a new GitlabSource. + * @param repoUrl The GitLab repository URL (e.g. "https://gitlab.com/user/repo"). + * @param accessToken Optional access token for private repositories. + * @param prerelease Whether to include pre-release versions. + */ + GitlabSource(const std::string& repoUrl, const std::string& accessToken = "", bool prerelease = false) + : IUpdateSourcePointer(vpkc_new_source_gitlab(repoUrl.c_str(), accessToken.empty() ? nullptr : accessToken.c_str(), prerelease)) { } +}; + +/** + * A built-in update source that reads release feeds and downloads assets from a Gitea repository. + */ +class GiteaSource : public IUpdateSourcePointer { +public: + /** + * Creates a new GiteaSource. + * @param repoUrl The Gitea repository URL (e.g. "https://gitea.example.com/user/repo"). + * @param accessToken Optional access token for private repositories. + * @param prerelease Whether to include pre-release versions. + */ + GiteaSource(const std::string& repoUrl, const std::string& accessToken = "", bool prerelease = false) + : IUpdateSourcePointer(vpkc_new_source_gitea(repoUrl.c_str(), accessToken.empty() ? nullptr : accessToken.c_str(), prerelease)) { } +}; + +/** + * A built-in update source that reads release feeds and downloads assets from Velopack Flow. + */ +class VelopackFlowSource : public IUpdateSourcePointer { +public: + /** + * Creates a new VelopackFlowSource. + * @param baseUri Optional base URI for the Velopack Flow API. If empty, the default URI is used. + */ + VelopackFlowSource(const std::string& baseUri = "") + : IUpdateSourcePointer(vpkc_new_source_velopack_flow(baseUri.empty() ? nullptr : baseUri.c_str())) { } +}; + +/** + * Provides functionality for checking for updates, downloading updates, and applying updates to the current application. + * This class is the main entry point for interacting with Velopack. + */ +class UpdateManager { +private: + vpkc_update_manager_t* m_pManager = 0; + std::unique_ptr m_pUpdateSource; + +public: + /** + * Create a new UpdateManager instance for a local or remote directory of releases. + * @param urlOrPath Location of the http update server or the local update directory path containing releases. + * @param options Optional extra configuration for update manager. + * @param locator Override the default locator configuration (usually used for testing / mocks). + */ + UpdateManager(const std::string& urlOrPath, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) { + vpkc_update_options_t* pOptions = alloc_c_UpdateOptions_ptr(options); + vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig_ptr(locator); + bool result = vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager); + free_c_UpdateOptions(pOptions); + free_c_VelopackLocatorConfig(pLocator); + if (!result) { + throw_last_error(); + } + }; + + /** + * Create a new UpdateManager instance with any update source (built-in or custom). + * The UpdateManager takes ownership of the source. + * @param pUpdateSource The source to use for retrieving feed and downloading assets. + * @param options Optional extra configuration for update manager. + * @param locator Override the default locator configuration (usually used for testing / mocks). + */ + template >> + UpdateManager(std::unique_ptr pUpdateSource, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) { + vpkc_update_options_t* pOptions = alloc_c_UpdateOptions_ptr(options); + vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig_ptr(locator); + m_pUpdateSource = std::unique_ptr(static_cast(pUpdateSource.release())); + vpkc_update_source_t* pSource = m_pUpdateSource->m_pSource; + bool result = vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager); + free_c_UpdateOptions(pOptions); + free_c_VelopackLocatorConfig(pLocator); + if (!result) { + throw_last_error(); + } + }; + + /** + * Destructor for UpdateManager. + */ + ~UpdateManager() { + if (m_pManager != nullptr) { + vpkc_free_update_manager(m_pManager); + m_pManager = nullptr; + } + }; + + /** + * Returns whether the app is in portable mode. On Windows this can be true or false. + * On MacOS and Linux this will always be true. + */ + bool IsPortable() noexcept { + return vpkc_is_portable(m_pManager); + }; + + /** + * Returns the currently installed version of the app. + */ + 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; + }; + + /** + * Returns the currently installed app id. + */ + 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; + }; + + /** + * Returns a VelopackAsset object if there is an update downloaded which still needs to be applied. + * You can pass this object to WaitExitThenApplyUpdates to apply the update. + * @returns A VelopackAsset object if there is a pending update, otherwise null. + */ + std::optional UpdatePendingRestart() noexcept { + vpkc_asset_t* asset; + if (vpkc_update_pending_restart(m_pManager, &asset)) { + VelopackAsset cpp_asset = to_cpp_VelopackAsset(asset).value(); + vpkc_free_asset(asset); + return cpp_asset; + } + return std::nullopt; + }; + + /** + * Checks for updates, returning null if there are none available. If there are updates available, this method will return an + * UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available. + * @returns An UpdateInfo object if there is an update available, otherwise null. + */ + 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::UPDATE_ERROR: + throw_last_error(); + return std::nullopt; + case vpkc_update_check_t::NO_UPDATE_AVAILABLE: + case vpkc_update_check_t::REMOTE_IS_EMPTY: + return std::nullopt; + case vpkc_update_check_t::UPDATE_AVAILABLE: + UpdateInfo cpp_info = to_cpp_UpdateInfo(update).value(); + vpkc_free_update_info(update); + return cpp_info; + } + return std::nullopt; + }; + + /** + * Downloads the specified updates to the local app packages directory. Progress is reported back to the caller via an optional callback. + * This function will acquire a global update lock so may fail if there is already another update operation in progress. + * - If the update contains delta packages and the delta feature is enabled + * this method will attempt to unpack and prepare them. + * - If there is no delta update available, or there is an error preparing delta + * packages, this method will fall back to downloading the full version of the update. + * @param update The update to download. + * @param progress A callback to report progress to. + * @param pUserData A pointer to user data that will be passed to the progress callback. + */ + void DownloadUpdates(const UpdateInfo& update, vpkc_progress_callback_t progress = nullptr, void* pUserData = 0) { + vpkc_update_info_t* vpkc_update = alloc_c_UpdateInfo(update); + bool result = vpkc_download_updates(m_pManager, vpkc_update, progress, pUserData); + free_c_UpdateInfo(vpkc_update); + if (!result) { + throw_last_error(); + } + }; + + /** + * This will launch the Velopack updater and tell it to wait for this program to exit gracefully. + * You should then clean up any state and exit your app. The updater will apply updates and then + * optionally restart your app. The updater will only wait for 60 seconds before giving up. + * @param asset The UpdateInfo object for the update to apply. + * @param silent If true, the updater will not show any UI. + * @param restart If true, the app will be restarted after the update is applied. + * @param restartArgs The arguments to pass to the app when it is restarted. + */ + void WaitExitThenApplyUpdates(const UpdateInfo& asset, bool silent = false, bool restart = true, std::vector restartArgs = {}) { + this->WaitExitThenApplyUpdates(asset.TargetFullRelease, silent, restart, restartArgs); + }; + + /** + * This will launch the Velopack updater and tell it to wait for this program to exit gracefully. + * You should then clean up any state and exit your app. The updater will apply updates and then + * optionally restart your app. The updater will only wait for 60 seconds before giving up. + * @param asset The update to apply. + * @param silent If true, the updater will not show any UI. + * @param restart If true, the app will be restarted after the update is applied. + * @param restartArgs The arguments to pass to the app when it is restarted. + */ + void WaitExitThenApplyUpdates(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector restartArgs = {}) { + size_t cRestartArgs; + char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs); + vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset); + bool result = vpkc_wait_exit_then_apply_updates(m_pManager, vpkc_asset, silent, restart, pRestartArgs, cRestartArgs); + free_c_string_vec(pRestartArgs, cRestartArgs); + free_c_VelopackAsset(vpkc_asset); + if (!result) { + throw_last_error(); + } + }; + + /** + * This will launch the Velopack updater and optionally wait for a program to exit gracefully. + * This method is unsafe because it does not necessarily wait for any / the correct process to exit + * before applying updates. The `WaitExitThenApplyUpdates` method is recommended for most use cases. + * If waitPid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended). + * @param asset The update to apply. + * @param silent If true, the updater will not show any UI. + * @param waitPid The process ID to wait for before applying updates. If 0, the updater will not wait. + * @param restart If true, the app will be restarted after the update is applied. + * @param restartArgs The arguments to pass to the app when it is restarted. + */ + void UnsafeApplyUpdates(const VelopackAsset& asset, bool silent, uint32_t waitPid, bool restart, std::vector restartArgs) { + size_t cRestartArgs; + char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs); + vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset); + bool result = vpkc_unsafe_apply_updates(m_pManager, vpkc_asset, silent, waitPid, restart, pRestartArgs, cRestartArgs); + free_c_string_vec(pRestartArgs, cRestartArgs); + free_c_VelopackAsset(vpkc_asset); + if (!result) { + throw_last_error(); + } + }; +}; + +} // namespace Velopack + +#endif // VELOPACK_HPP diff --git a/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll b/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll new file mode 100644 index 0000000000..84a52af59a Binary files /dev/null and b/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll differ diff --git a/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll.lib b/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll.lib new file mode 100644 index 0000000000..a66af364f4 Binary files /dev/null and b/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll.lib differ diff --git a/TeXmacs/progs/utils/misc/updater-test.scm b/TeXmacs/progs/utils/misc/updater-test.scm new file mode 100644 index 0000000000..8c7466013f --- /dev/null +++ b/TeXmacs/progs/utils/misc/updater-test.scm @@ -0,0 +1,33 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : updater-test.scm +;; DESCRIPTION : 纯逻辑单元测试:更新器 idle 默认值。不触发任何网络检查, +;; 跨平台安全——非 Windows 上基础 tm_updater 的默认实现同样 +;; 满足这些断言。 +;; COPYRIGHT : (C) 2026 Mogan STEM +;; +;; USAGE +;; xmake b stem +;; xmake r updater-test +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(import (liii check)) + +(check-set-mode! 'report-failed) + +(load "./TeXmacs/progs/utils/misc/updater.scm") + +;; idle 状态下:state=0 (UPDATER_IDLE),版本/错误码为空,进度 0, +;; download/apply 均返回 #f(未到可用/就绪状态)。 + +(define (test-updater-idle) + (check (updater-state) => 0) + (check (updater-available-version) => "") + (check (updater-progress) => 0) + (check (updater-error-code) => "") + (check (updater-download) => #f) + (check (updater-apply) => #f) +) ;define + +(tm-define (regtest-updater) (test-updater-idle) (check-report)) diff --git a/devel/0512.md b/devel/0512.md new file mode 100644 index 0000000000..475d177d6c --- /dev/null +++ b/devel/0512.md @@ -0,0 +1,47 @@ +# 0512: 迁移到 Velopack 打包与自动更新 + +## 2026/08/07 迁移到 Velopack 打包与自动更新 + +### What(本次改动做了什么) + +把 Mogan STEM 的 Windows 发布与自动更新从"客户端下载完整 NSIS 安装包后静默执行"迁移到 +Velopack:以 Velopack 作为安装/更新事务的唯一所有者,产出 `Setup.exe`/MSI 与 `releases..json` +静态 feed + delta 增量包;应用内通过 Velopack C++ runtime 检查、后台下载、用户确认后安装并重启。 + +### Why(为什么这么做) + +- 常规升级路径下载整包且静默执行,更新体积大、耗时长;Velopack delta 让已装用户只下载增量。 +- 文件占用、版本目录替换、重启与 delta 链选择交给 Velopack,避免自研 `MoganUpdater`/HDiffPatch/提权 worker。 +- 三平台共用 Velopack 发布版本、渠道与客户端更新 API,各平台仍产出各自安装格式。 + +### How(怎么做的) + +1. **C++ runtime 集成(Windows)**:`velopack_libc_1.2.0` vendor 到 `3rdparty/velopack/`; + xmake 为 `stem` 引入头文件/导入库/动态库;`research.cpp` 的 `main()` 首行 + `Velopack::VelopackApp::Build().Run()`(早于 `lolly::init_tbox()` 等副作用)。 +2. **tm_velopack 抽象**:保留 `tm_updater` 内部边界,新增状态机 + `idle/checking/available/downloading/ready/applying/failed` 与版本/进度/错误码/发行说明, + 以及 `downloadUpdate`/`applyUpdate`;`instance()` 在 Windows 下走 `USE_PLUGIN_VELOPACK` 分支, + WinSparkle/Sparkle 原分支不变。 +3. **Scheme/Qt 接线**:`updater.scm` 后台检查改走异步 `tm_velopack`,`init-research.scm` idle 入口保留; + Qt/QML controller 订阅状态并暴露下载进度与"重启以更新"。 +4. **打包流水线**:`tools/release/stage_velopack.lua` 组装干净 staging 并做清单校验; + `tools/release/pack_velopack.lua` 封装 `vpk pack`(`--packId Mogan --packVersion `); + NSIS xpack 保留为迁移期桥接。 +5. **NSIS 桥接**:一次性发布签名的桥接 NSIS 包,保存 `$TEXMACS_HOME_PATH`、关闭旧版、启动 Velopack Setup; + 迁移期结束后停发 NSIS 更新。 + +### 涉及文件 + +- `src/Mogan/Research/research.cpp`、`xmake/targets/stem.lua`、`xmake/targets/libmogan.lua` +- `src/Plugins/Updater/tm_updater.*`、`tm_velopack.*`(新增)、`src/Scheme/Plugins/glue_updater.lua` +- `TeXmacs/progs/utils/misc/updater.scm`、`TeXmacs/progs/init-research.scm`、`src/Plugins/Qt/TmUpdaterController.*` +- `tools/release/stage_velopack.lua`、`tools/release/pack_velopack.lua`(新增) +- `.github/workflows/cd_research_on_windows.yml` + +### 验证门槛(POC) + +- 已装包能启动;Qt DLL/QML、`TeXmacs/progs`、字体、Scheme 插件资源路径正常。 +- 应用内检查、下载、退出、替换、重启成功,无需手动运行更新文件。 +- 连续版本生成 delta;delta 效益、回退、文件占用、SafeNet 签名、NSIS 迁移逐项验证通过后才全面启用。 + diff --git a/src/Mogan/Research/research.cpp b/src/Mogan/Research/research.cpp index 3f1e089492..e59d4e0454 100644 --- a/src/Mogan/Research/research.cpp +++ b/src/Mogan/Research/research.cpp @@ -40,6 +40,10 @@ #include "tm_url.hpp" #include "tm_window.hpp" +#if defined(OS_WIN) +#include "Velopack.hpp" +#endif + #ifdef AQUATEXMACS void mac_fix_paths (); #endif @@ -161,6 +165,12 @@ immediate_options (int argc, char** argv) { int main (int argc, char** argv) { + // Velopack 启动钩子:处理待安装的更新(无安装时为空操作)。 + // 必须早于任何系统初始化与参数解析,且仅限 Windows。 +#if defined(OS_WIN) + Velopack::VelopackApp::Build ().Run (); +#endif + // 1.系统初始化 lolly::init_tbox (); // 初始化tbox库 lolly::system::args a (argc, argv); // 解决Windows平台命令行参数的编码转换问题 diff --git a/src/Plugins/Updater/tm_updater.cpp b/src/Plugins/Updater/tm_updater.cpp index 7b51fda4a9..0fb0f28044 100644 --- a/src/Plugins/Updater/tm_updater.cpp +++ b/src/Plugins/Updater/tm_updater.cpp @@ -13,12 +13,20 @@ #include "config.h" #include "scheme.hpp" +#if (defined(OS_MINGW) || defined(OS_WIN)) && defined(USE_PLUGIN_VELOPACK) +#include "tm_velopack.hpp" +#endif + tm_updater* tm_updater::instance () { static tm_updater* _instance= NULL; if (!_instance) { +#if (defined(OS_MINGW) || defined(OS_WIN)) && defined(USE_PLUGIN_VELOPACK) + _instance= new tm_velopack (); +#else _instance= new tm_updater (); +#endif } ASSERT (_instance != NULL, "Unable to instantiate updater."); @@ -58,3 +66,45 @@ updater_last_check () { tm_updater* updater= tm_updater::instance (); return updater ? updater->lastCheck () : 0; } + +int +updater_state () { + tm_updater* updater= tm_updater::instance (); + return updater ? updater->state () : UPDATER_IDLE; +} + +string +updater_available_version () { + tm_updater* updater= tm_updater::instance (); + return updater ? updater->availableVersion () : string (); +} + +string +updater_release_notes () { + tm_updater* updater= tm_updater::instance (); + return updater ? updater->releaseNotes () : string (); +} + +int +updater_progress () { + tm_updater* updater= tm_updater::instance (); + return updater ? updater->progress () : 0; +} + +string +updater_error_code () { + tm_updater* updater= tm_updater::instance (); + return updater ? updater->errorCode () : string (); +} + +bool +updater_download () { + tm_updater* updater= tm_updater::instance (); + return updater && updater->downloadUpdate (); +} + +bool +updater_apply () { + tm_updater* updater= tm_updater::instance (); + return updater && updater->applyUpdate (); +} diff --git a/src/Plugins/Updater/tm_updater.hpp b/src/Plugins/Updater/tm_updater.hpp index 6c10169ab5..dac0e09c67 100644 --- a/src/Plugins/Updater/tm_updater.hpp +++ b/src/Plugins/Updater/tm_updater.hpp @@ -14,6 +14,22 @@ #include "url.hpp" #include +/****************************************************************************** + * Auto-update state machine + ******************************************************************************/ + +// 注意:枚举类型名不能叫 updater_state——同名 Scheme 接口函数 +// (updater_state) 在类作用域会遮蔽标签名,导致 MSVC 语法错误。 +enum tm_updater_state { + UPDATER_IDLE= 0, + UPDATER_CHECKING, + UPDATER_AVAILABLE, + UPDATER_DOWNLOADING, + UPDATER_READY, + UPDATER_APPLYING, + UPDATER_FAILED +}; + class tm_updater { protected: static const int MinimumCheckInterval= 24; //. + ******************************************************************************/ + +#include "tm_configure.hpp" + +#if defined(USE_PLUGIN_VELOPACK) && (defined(OS_MINGW) || defined(OS_WIN)) + +#include "string.hpp" +#include "tm_velopack.hpp" + +#include + +// Velopack 桥接层使用 C++ 标准库线程原语与容器,工作线程不触碰 lolly 类型 +// (与 src/Plugins/WebSocket/libcurl/tm_curl_websocket_client.* 同理)。 +#include +#include +#include +#include +#include +#include + +static std::string +exception_message () { + try { + throw; + } catch (std::exception& e) { + return e.what (); + } catch (...) { + return "unknown error"; + } +} + +struct tm_velopack::tm_velopack_rep { + Velopack::UpdateManager* mgr; // 惰性创建 + std::optional info; // 最近一次检查结果 + std::thread worker; // 当前检查/下载线程 + std::mutex mtx; // 保护以下字段 + tm_updater_state st; // = UPDATER_IDLE + std::string version; // 目标版本 + std::string notes; // 发行说明 (markdown) + std::string error; // 错误码/消息 + int progress; // 0..100 + time_t last; // 最近检查时间 + bool running; // 是否有线程在跑 + std::string feed_url; // 更新源 + + tm_velopack_rep () + : mgr (nullptr), st (UPDATER_IDLE), progress (0), last (0), + running (false), + // TODO: feed 地址占位符(正式地址待定,发布前替换) + feed_url ("https://feed.invalid/mogan/windows-x64/stable") {} + ~tm_velopack_rep () { + if (worker.joinable ()) worker.join (); + delete mgr; + } +}; + +tm_velopack::tm_velopack () : rep (new tm_velopack_rep ()) {} + +tm_velopack::~tm_velopack () { delete rep; } + +bool +tm_velopack::setAppcast (url _url) { + std::lock_guard lk (rep->mtx); + rep->feed_url= std::string ((const char*) c_string (as_string (_url))); + return true; +} + +void +tm_velopack::ensure_mgr () { + std::lock_guard lk (rep->mtx); + if (!rep->mgr) rep->mgr= new Velopack::UpdateManager (rep->feed_url); +} + +bool +tm_velopack::checkInBackground () { + std::lock_guard lk (rep->mtx); + if (rep->running) return false; + if (rep->worker.joinable ()) rep->worker.join (); + rep->st = UPDATER_CHECKING; + rep->running= true; + rep->worker = std::thread ([this] { do_check (); }); + return true; +} + +bool +tm_velopack::checkInForeground () { + return checkInBackground (); +} + +bool +tm_velopack::isRunning () const { + std::lock_guard lk (rep->mtx); + return rep->running; +} + +time_t +tm_velopack::lastCheck () const { + std::lock_guard lk (rep->mtx); + return rep->last; +} + +bool +tm_velopack::setCheckInterval (int hours) { + interval= hours; + return true; +} + +void +tm_velopack::do_check () { + try { + ensure_mgr (); + std::optional u= rep->mgr->CheckForUpdates (); + std::lock_guard lk (rep->mtx); + if (u) { + rep->info = u; + rep->st = UPDATER_AVAILABLE; + rep->version= u->TargetFullRelease.Version; + rep->notes = u->TargetFullRelease.NotesMarkdown; + } + else { + rep->st= UPDATER_IDLE; + } + rep->last = time (NULL); + rep->running= false; + } catch (...) { + std::lock_guard lk (rep->mtx); + rep->st = UPDATER_FAILED; + rep->error = exception_message (); + rep->running= false; + } +} + +tm_updater_state +tm_velopack::state () const { + std::lock_guard lk (rep->mtx); + return rep->st; +} + +string +tm_velopack::availableVersion () const { + std::lock_guard lk (rep->mtx); + return string (rep->version.c_str ()); +} + +string +tm_velopack::releaseNotes () const { + std::lock_guard lk (rep->mtx); + return string (rep->notes.c_str ()); +} + +int +tm_velopack::progress () const { + std::lock_guard lk (rep->mtx); + return rep->progress; +} + +string +tm_velopack::errorCode () const { + std::lock_guard lk (rep->mtx); + return string (rep->error.c_str ()); +} + +bool +tm_velopack::downloadUpdate () { + std::lock_guard lk (rep->mtx); + if (rep->st != UPDATER_AVAILABLE) return false; + if (rep->running) return false; + if (rep->worker.joinable ()) rep->worker.join (); + rep->st = UPDATER_DOWNLOADING; + rep->running= true; + rep->worker = std::thread ([this] { do_download (); }); + return true; +} + +void +tm_velopack::progress_cb (void* user_data, size_t progress) { + tm_velopack* self= static_cast (user_data); + std::lock_guard lk (self->rep->mtx); + self->rep->progress= static_cast (progress); +} + +void +tm_velopack::do_download () { + try { + ensure_mgr (); + rep->mgr->DownloadUpdates (*rep->info, &tm_velopack::progress_cb, this); + std::lock_guard lk (rep->mtx); + rep->st = UPDATER_READY; + rep->running= false; + } catch (...) { + std::lock_guard lk (rep->mtx); + rep->st = UPDATER_FAILED; + rep->error = exception_message (); + rep->running= false; + } +} + +bool +tm_velopack::applyUpdate () { + { + std::lock_guard lk (rep->mtx); + if (rep->st != UPDATER_READY || !rep->info) return false; + rep->st= UPDATER_APPLYING; + } + try { + rep->mgr->WaitExitThenApplyUpdates (rep->info->TargetFullRelease, + /*silent*/ false, /*restart*/ true); + } catch (...) { + std::lock_guard lk (rep->mtx); + rep->st = UPDATER_FAILED; + rep->error= exception_message (); + return false; + } + exit (0); + return true; // 不可达:成功时进程退出,由更新器应用并重启 +} + +#endif // defined (USE_PLUGIN_VELOPACK) && (defined (OS_MINGW) || defined + // (OS_WIN)) diff --git a/src/Plugins/Updater/tm_velopack.hpp b/src/Plugins/Updater/tm_velopack.hpp new file mode 100644 index 0000000000..4c6271b537 --- /dev/null +++ b/src/Plugins/Updater/tm_velopack.hpp @@ -0,0 +1,54 @@ +/****************************************************************************** + * MODULE : tm_velopack.hpp + * DESCRIPTION: Manager class for the autoupdater Velopack framework + * COPYRIGHT : (C) 2026 Mogan + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#ifndef TM_VELOPACK_HPP +#define TM_VELOPACK_HPP + +#include "tm_updater.hpp" +#include + +/** + * @brief 基于 Velopack C++ 运行时的自动更新器。 + * + * 采用 pimpl 封装:内部实现(tm_velopack_rep)在 .cpp 中定义,头文件不依赖 + * Velopack.hpp,避免把 std::vector 等标准库类型泄漏到 glue 等编译单元。 + */ +class tm_velopack : public tm_updater { + struct tm_velopack_rep; // 内部实现,定义在 .cpp + tm_velopack_rep* rep; + + tm_velopack (); + ~tm_velopack (); + friend class tm_updater; + + void do_check (); // 工作线程:检查更新 + void do_download (); // 工作线程:下载更新 + void ensure_mgr (); // 惰性创建 UpdateManager + static void progress_cb (void* user_data, + size_t progress); // Velopack 进度回调 + +public: + bool checkInBackground (); + bool checkInForeground (); + bool isRunning () const; + time_t lastCheck () const; + bool setCheckInterval (int hours); + bool setAppcast (url _url); + + tm_updater_state state () const; + string availableVersion () const; + string releaseNotes () const; + int progress () const; + string errorCode () const; + bool downloadUpdate (); + bool applyUpdate (); +}; + +#endif // TM_VELOPACK_HPP diff --git a/src/Scheme/Plugins/glue_plugin_extra.hpp b/src/Scheme/Plugins/glue_plugin_extra.hpp index f7c06dd79e..512a468be9 100644 --- a/src/Scheme/Plugins/glue_plugin_extra.hpp +++ b/src/Scheme/Plugins/glue_plugin_extra.hpp @@ -10,7 +10,11 @@ inline bool use_plugin_updater () { +#if defined(USE_PLUGIN_VELOPACK) + return true; +#else return false; +#endif } inline bool diff --git a/src/Scheme/Plugins/glue_updater.lua b/src/Scheme/Plugins/glue_updater.lua index 514dcb402b..2a5e920757 100644 --- a/src/Scheme/Plugins/glue_updater.lua +++ b/src/Scheme/Plugins/glue_updater.lua @@ -51,6 +51,41 @@ function main() "int" } }, + { + scm_name = "updater-state", + cpp_name = "updater_state", + ret_type = "int" + }, + { + scm_name = "updater-available-version", + cpp_name = "updater_available_version", + ret_type = "string" + }, + { + scm_name = "updater-release-notes", + cpp_name = "updater_release_notes", + ret_type = "string" + }, + { + scm_name = "updater-progress", + cpp_name = "updater_progress", + ret_type = "int" + }, + { + scm_name = "updater-error-code", + cpp_name = "updater_error_code", + ret_type = "string" + }, + { + scm_name = "updater-download", + cpp_name = "updater_download", + ret_type = "bool" + }, + { + scm_name = "updater-apply", + cpp_name = "updater_apply", + ret_type = "bool" + }, } } end diff --git a/src/Scheme/Plugins/init_glue_plugins.cpp b/src/Scheme/Plugins/init_glue_plugins.cpp index 57433f129c..aaeef3a43c 100644 --- a/src/Scheme/Plugins/init_glue_plugins.cpp +++ b/src/Scheme/Plugins/init_glue_plugins.cpp @@ -45,6 +45,10 @@ initialize_glue_plugins () { initialize_glue_html (); #endif +#if defined(USE_PLUGIN_VELOPACK) + initialize_glue_updater (); +#endif + #ifdef USE_PLUGIN_BIBTEX initialize_glue_bibtex (); #endif diff --git a/src/System/config.h.xmake b/src/System/config.h.xmake index 1ba08eecf5..61dcb30131 100644 --- a/src/System/config.h.xmake +++ b/src/System/config.h.xmake @@ -63,6 +63,7 @@ ${define USE_PLUGIN_PDF} ${define USE_PLUGIN_BIBTEX} ${define USE_PLUGIN_TEX} ${define USE_PLUGIN_ISPELL} +${define USE_PLUGIN_VELOPACK} ${define USE_PLUGIN_HTML} ${define USE_PLUGIN_GIT} diff --git a/tools/velopack/velopack_startup_test.cpp b/tools/velopack/velopack_startup_test.cpp new file mode 100644 index 0000000000..60a95b4947 --- /dev/null +++ b/tools/velopack/velopack_startup_test.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +// 仅验证 Velopack C++ runtime 启动钩子可编译/链接;未安装环境下 Run() 为空操作。 +int main () { +#if defined (_WIN32) && defined (_M_X64) + try { + Velopack::VelopackApp::Build ().Run (); + std::cout << "velopack startup hook: ok\n"; + } catch (const std::exception &e) { + std::cerr << "velopack startup hook failed: " << e.what () << "\n"; + return 1; + } +#endif + return 0; +} diff --git a/xmake.lua b/xmake.lua index 70b9fb140e..9b98b468f4 100644 --- a/xmake.lua +++ b/xmake.lua @@ -20,6 +20,7 @@ includes("lolly") includes("xmake/stem.lua") includes("xmake/rules/glue.lua") includes("xmake/goldfish.lua") +includes("xmake/velopack.lua") set_project(stem_project_name) set_policy("run.autobuild", false) diff --git a/xmake/targets/libmogan.lua b/xmake/targets/libmogan.lua index 59b4ee5dd9..9df226c2ac 100644 --- a/xmake/targets/libmogan.lua +++ b/xmake/targets/libmogan.lua @@ -21,6 +21,11 @@ target("libmogan") do set_runtimes("MT") add_defines("_USE_MATH_DEFINES") end + if is_plat("windows") and is_arch("x64") then + -- Velopack C++ runtime:头文件路径供后续 tm_velopack 使用;链接项对静态库仅 + -- 在最终 exe 链接时生效,stem 已自带,此处双保险。 + add_velopack_runtime () + end set_languages("c++17") set_policy("check.auto_ignore_flags", false) set_encodings("utf-8") @@ -154,6 +159,7 @@ target("libmogan") do USE_PLUGIN_TEX = true, USE_PLUGIN_ISPELL = true, USE_PLUGIN_PDF = has_config("pdfhummus"), + USE_PLUGIN_VELOPACK = is_plat("windows") and is_arch("x64"), USE_PLUGIN_HTML = true, USE_MUPDF_RENDERER = has_config("mupdf"), USE_STARTUP_TAB = has_config("startup_tab"), diff --git a/xmake/targets/stem.lua b/xmake/targets/stem.lua index 40a6d7cf96..d3616322b9 100644 --- a/xmake/targets/stem.lua +++ b/xmake/targets/stem.lua @@ -183,6 +183,14 @@ target("stem") do add_includedirs("$(builddir)", {public = true}) add_files("$(projectdir)/src/Mogan/Research/research.cpp") + -- Velopack C++ runtime:启动钩子编译/链接 + 动态库随 bin/ 发布 + if is_plat("windows") and is_arch("x64") then + add_velopack_runtime () + -- 导入库内嵌 DLL 名为 velopack_libc.dll,发布时改名,exe 才能加载 + add_installfiles ("$(projectdir)/3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll", + {prefixdir = "bin", filename = "velopack_libc.dll"}) + end + -- install tm files for testing purpose if is_mode("releasedbg") then if is_plat("mingw", "windows") then diff --git a/xmake/velopack.lua b/xmake/velopack.lua new file mode 100644 index 0000000000..ff08d48181 --- /dev/null +++ b/xmake/velopack.lua @@ -0,0 +1,33 @@ +-- Velopack C/C++ runtime(Windows x64)集成。 +-- add_velopack_runtime () 仅供 target("stem") / target("libmogan") 块内调用: +-- xmake 的 target 作用域 API(add_includedirs/add_links/...)绑定当前活动 target, +-- 普通 Lua 函数在 target 块内调用即可生效。 + +function add_velopack_runtime () + if not (is_plat ("windows") and is_arch ("x64")) then return end + add_includedirs ("$(projectdir)/3rdparty/velopack/include") + add_linkdirs ("$(projectdir)/3rdparty/velopack/lib") + -- 链接导入库:文件名形如 velopack_libc_win_x64_msvc.dll.lib + add_links ("velopack_libc_win_x64_msvc.dll") +end + +-- 最小验证程序:仅验证 Velopack C++ 启动钩子可编译/链接,未安装环境下 Run() 为空操作。 +-- 不放在 tests/ 下:根 xmake.lua 会自动发现 tests/**_test.cpp 并链接 libmogan/libmoebius, +-- 会与此处目标重名冲突,且该验证程序不应依赖项目库。 +if is_plat ("windows") and is_arch ("x64") then + target ("velopack_startup_test") do + set_kind ("binary") + set_group ("velopack") + set_languages ("c++17") + set_runtimes ("MT") + add_velopack_runtime () + add_files ("$(projectdir)/tools/velopack/velopack_startup_test.cpp") + -- 运行时需在 exe 旁找到 DLL。导入库内嵌的 DLL 名为 velopack_libc.dll, + -- 故发布时须把 vendored 的 velopack_libc_win_x64_msvc.dll 改名为 velopack_libc.dll。 + -- (after_build 不替代默认编译/链接) + after_build (function (target) + os.cp (path.join (os.projectdir (), "3rdparty/velopack/lib/velopack_libc_win_x64_msvc.dll"), + path.join (target:targetdir (), "velopack_libc.dll")) + end) + end +end