From 304eac3b71db3277aa0d6aa10ac793d4c05ab040 Mon Sep 17 00:00:00 2001 From: Karthik Ravikanti Date: Thu, 12 Dec 2024 12:04:54 +0800 Subject: [PATCH] Add support for custom update sources in the C++ library --- Cargo.lock | 1 + src/lib-cpp/Cargo.toml | 1 + src/lib-cpp/include/Velopack.h | 24 +++++++++ src/lib-cpp/include/Velopack.hpp | 58 +++++++++++++++++++- src/lib-cpp/src/lib.rs | 93 +++++++++++++++++++++++++++++++- 5 files changed, 174 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbc46bb8..5c24244f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2123,6 +2123,7 @@ dependencies = [ "lazy_static", "libc", "log", + "serde_json", "velopack", ] diff --git a/src/lib-cpp/Cargo.toml b/src/lib-cpp/Cargo.toml index 4b305302..326e4640 100644 --- a/src/lib-cpp/Cargo.toml +++ b/src/lib-cpp/Cargo.toml @@ -23,6 +23,7 @@ anyhow.workspace = true lazy_static.workspace = true log.workspace = true libc.workspace = true +serde_json.workspace = true [build-dependencies] cbindgen.workspace = true \ No newline at end of file diff --git a/src/lib-cpp/include/Velopack.h b/src/lib-cpp/include/Velopack.h index 24447d07..9030a578 100644 --- a/src/lib-cpp/include/Velopack.h +++ b/src/lib-cpp/include/Velopack.h @@ -175,6 +175,30 @@ bool vpkc_new_update_manager(const char *psz_url_or_path, struct vpkc_locator_config_t *p_locator, vpkc_update_manager_t **p_manager); +/** + * Create a new UpdateManager instance. + * @param options Optional extra configuration for update manager. + * @param locator Override the default locator configuration (usually used for testing / mocks). + * @param callback to override the default update source + * (AutoSource). Retrieve the list of available remote releases from + * the package source. These releases can subsequently be downloaded + * with cb_download_release_entry. + * @param callback to override the default update source + * (AutoSource). Download the specified VelopackAsset to the provided + * local file path. + * @param parameter to the callbacks to override the default update + * source (AutoSource). It's the user's responsibilty to ensure that + * it's safe to send and share it across threads + */ +bool vpkc_new_custom_update_manager(const char *(*cb_get_release_feed)(const char*, void*), + bool (*cb_download_release_entry)(const char*, + const char*, + void*), + void *p_user_data, + 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. */ diff --git a/src/lib-cpp/include/Velopack.hpp b/src/lib-cpp/include/Velopack.hpp index 66377e33..298450cf 100644 --- a/src/lib-cpp/include/Velopack.hpp +++ b/src/lib-cpp/include/Velopack.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "Velopack.h" @@ -333,12 +334,31 @@ public: }; }; +/** + * Implement this interface override the default update source + * (AutoSource) + */ +class IUpdateSource { +public: + /** @param Retrieve the list of available remote releases from + * the package source. These releases can subsequently be downloaded + * with DownloadReleaseEntry(). + */ + virtual const char* GetReleaseFeed(const char* releasesName) = 0; + + /** @param Download the specified VelopackAsset to the provided local file path. + */ + virtual bool DownloadReleaseEntry(const char* assetFileName, const char* localFilePath) = 0; +}; + /** * Provides functionality for checking for updates, downloading updates, and applying updates to the current application. */ class UpdateManager { private: vpkc_update_manager_t* m_pManager = 0; + std::unique_ptr m_pUpdateSource; + public: /** * Create a new UpdateManager instance. @@ -366,6 +386,42 @@ public: } }; + /** + * Create a new UpdateManager instance. + * @param pUpdateSource Custom update source implementation. + * @param options Optional extra configuration for update manager. + * @param locator Override the default locator configuration (usually used for testing / mocks). + */ + UpdateManager(std::unique_ptr pUpdateSource, 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; + } + + auto cbGetReleaseFeed = [](const char* releasesName, void* userData) { + IUpdateSource* source = reinterpret_cast(userData); + return source->GetReleaseFeed(releasesName); + }; + + auto cbDownloadReleaseEntry = [](const char* assetFileName, const char* localFilePath, void* userData) { + IUpdateSource* source = reinterpret_cast(userData); + return source->DownloadReleaseEntry(assetFileName, localFilePath); + }; + + m_pUpdateSource.swap(pUpdateSource); + + if (!vpkc_new_custom_update_manager(cbGetReleaseFeed, cbDownloadReleaseEntry, m_pUpdateSource.get(), pOptions, pLocator, &m_pManager)) { + throw_last_error(); + } + }; + /** * Destructor for UpdateManager. */ @@ -480,4 +536,4 @@ public: } // namespace Velopack -#endif // VELOPACK_HPP \ No newline at end of file +#endif // VELOPACK_HPP diff --git a/src/lib-cpp/src/lib.rs b/src/lib-cpp/src/lib.rs index c6f5c7a1..385af78f 100644 --- a/src/lib-cpp/src/lib.rs +++ b/src/lib-cpp/src/lib.rs @@ -10,8 +10,8 @@ use types::*; use anyhow::{anyhow, bail}; use libc::{c_char, c_void, size_t}; -use std::ffi::CString; -use velopack::{sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp}; +use std::{sync::mpsc::Sender, ffi::{CStr, CString}}; +use velopack::{bundle, sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp, VelopackAsset, VelopackAssetFeed}; /// Create a new UpdateManager instance. /// @param urlOrPath Location of the update server or path to the local update directory. @@ -35,6 +35,95 @@ pub extern "C" fn vpkc_new_update_manager( }) } +#[derive(Clone)] +/// Retrieves available updates using a custom method. This is +/// intended to be used from C and C++ code when `AutoSource` is +/// inadequate. +struct CCallbackUpdateSource { + /// Opaque data that's passed to the callbacks + p_user_data: *mut c_void, + /// Callback function that returns the requrested asset feed JSON as a string + cb_get_release_feed: extern "C" fn(psz_releases_name: *const c_char, p_user_data: *mut c_void) -> *const c_char, + /// Callback function that downloads the requested asset file to a local path + cb_download_release_entry: extern "C" fn(psz_asset_file_name: *const c_char, psz_local_file: *const c_char, p_user_data: *mut c_void) -> bool, +} + +unsafe impl Send for CCallbackUpdateSource {} +unsafe impl Sync for CCallbackUpdateSource {} + +impl sources::UpdateSource for CCallbackUpdateSource { + fn get_release_feed(&self, channel: &str, _: &bundle::Manifest) -> Result { + let releases_name = format!("releases.{}.json", channel); + let releases_name_cstr = CString::new(releases_name).unwrap(); + let json_cstr_ptr = (self.cb_get_release_feed)(releases_name_cstr.as_ptr(), self.p_user_data); + if json_cstr_ptr.is_null() { + Err(VelopackError::Generic("Failed to fetch releases JSON".to_owned())) + } else { + let json_cstr = unsafe { CStr::from_ptr(json_cstr_ptr) }; + let feed: VelopackAssetFeed = serde_json::from_str(json_cstr.to_str().unwrap())?; + Ok(feed) + } + } + + fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option>) -> Result<(), VelopackError> { + if let Some(progress_sender) = &progress_sender { + let _ = progress_sender.send(50); + } + let asset_file_name_cstr = CString::new(asset.FileName.as_str()).unwrap(); + let local_file_cstr = CString::new(local_file).unwrap(); + let success = (self.cb_download_release_entry)(asset_file_name_cstr.as_ptr(), local_file_cstr.as_ptr(), self.p_user_data); + if success { + if let Some(progress_sender) = &progress_sender { + let _ = progress_sender.send(100); + } + Ok(()) + } else { + Err(VelopackError::Generic("Failed to download asset file".to_owned())) + } + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } +} + + +/// Create a new UpdateManager instance. +/// @param options Optional extra configuration for update manager. +/// @param locator Override the default locator configuration (usually used for testing / mocks). +/// @param callback to override the default update source +/// (AutoSource). Retrieve the list of available remote releases from +/// the package source. These releases can subsequently be downloaded +/// with cb_download_release_entry. +/// @param callback to override the default update source +/// (AutoSource). Download the specified VelopackAsset to the provided +/// local file path. +/// @param parameter to the callbacks to override the default update +/// source (AutoSource). It's the user's responsibilty to ensure that +/// it's safe to send and share it across threads +#[no_mangle] +pub extern "C" fn vpkc_new_custom_update_manager( + cb_get_release_feed: extern "C" fn(*const c_char, *mut c_void) -> *const c_char, + cb_download_release_entry: extern "C" fn(*const c_char, *const c_char, *mut c_void) -> bool, + p_user_data: *mut c_void, + p_options: *mut vpkc_update_options_t, + p_locator: *mut vpkc_locator_config_t, + p_manager: *mut *mut vpkc_update_manager_t, +) -> bool { + wrap_error(|| { + let source = CCallbackUpdateSource { + p_user_data, + cb_get_release_feed, + cb_download_release_entry, + }; + let options = c_to_updateoptions_opt(p_options); + let locator = c_to_velopacklocatorconfig_opt(p_locator); + let manager = UpdateManager::new(source, options, locator)?; + unsafe { *p_manager = UpdateManagerRawPtr::new(manager) }; + Ok(()) + }) +} + /// Returns the currently installed version of the app. #[no_mangle] pub extern "C" fn vpkc_get_current_version(p_manager: *mut vpkc_update_manager_t, psz_version: *mut c_char, c_version: size_t) -> size_t {