diff --git a/src/lib-cpp/include/Velopack.h b/src/lib-cpp/include/Velopack.h index b138ef77..469a1934 100644 --- a/src/lib-cpp/include/Velopack.h +++ b/src/lib-cpp/include/Velopack.h @@ -35,7 +35,12 @@ 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 const char *(*vpkc_release_feed_delegate_t)(void *p_user_data, const char *psz_releases_name); +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. @@ -203,6 +208,7 @@ vpkc_update_source_t *vpkc_new_source_http_url(const char *psz_http_url); * Therefore to avoid possible issues, it is recommended to create this type of source once for the lifetime of your application. */ 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); diff --git a/src/lib-cpp/include/Velopack.hpp b/src/lib-cpp/include/Velopack.hpp index 298450cf..16a65941 100644 --- a/src/lib-cpp/include/Velopack.hpp +++ b/src/lib-cpp/include/Velopack.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "Velopack.h" @@ -192,23 +193,32 @@ static inline UpdateOptions to_cpp(const vpkc_update_options_t& dto) { } // !! AUTO-GENERATED-END CPP_TYPES -static inline char** to_cstring_array(const std::vector& vec) { +static inline char* allocate_cstring(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 void free_cstring(char* str) { + delete[] str; +} + +static inline char** allocate_cstring_array(const std::vector& vec) { char** result = new char*[vec.size()]; for (size_t i = 0; i < vec.size(); ++i) { - result[i] = new char[vec[i].size() + 1]; // +1 for null-terminator -#ifdef _WIN32 - strcpy_s(result[i], vec[i].size() + 1, vec[i].c_str()); // Copy string content -#else - strcpy(result[i], vec[i].c_str()); // Copy string content -#endif - result[i][vec[i].size()] = '\0'; // Null-terminate the string + result[i] = allocate_cstring(vec[i]); } return result; } static inline void free_cstring_array(char** arr, size_t size) { for (size_t i = 0; i < size; ++i) { - delete[] arr[i]; + free_cstring(arr[i]); arr[i] = nullptr; } delete[] arr; @@ -250,7 +260,7 @@ public: * Override the command line arguments used by VelopackApp. (by default this is env::args().skip(1)) */ VelopackApp& SetArgs(const std::vector& args) { - char** pArgs = to_cstring_array(args); + char** pArgs = allocate_cstring_array(args); vpkc_app_set_args(pArgs, args.size()); free_cstring_array(pArgs, args.size()); return *this; @@ -335,20 +345,65 @@ public: }; /** - * Implement this interface override the default update source - * (AutoSource) + * Progress callback function. Call with values between 0 and 100 inclusive. + */ +typedef std::function vpkc_progress_send_t; + +/** + * Abstract class for retrieving release feeds and downloading assets. You should subclass this and + * implement/override the GetReleaseFeed and DownloadReleaseEntry methods. + * This class is used by the UpdateManager to fetch release feeds and download assets in a custom way. */ class IUpdateSource { + friend class UpdateManager; + friend class FileSource; + friend class HttpSource; +private: + IUpdateSource(vpkc_update_source_t* pSource) : m_pSource(pSource) {} + vpkc_update_source_t* m_pSource = 0; 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; + ~IUpdateSource() { + vpkc_free_source(m_pSource); + } + IUpdateSource() { + m_pSource = vpkc_new_source_custom_callback( + [](void* userData, const char* releasesName) { + IUpdateSource* source = reinterpret_cast(userData); + std::string json = source->GetReleaseFeed(releasesName); + return allocate_cstring(json); + }, + [](void* userData, char* pszFeed) { + free_cstring(pszFeed); + }, + [](void* userData, const struct vpkc_asset_t *pAsset, const char* pszLocalPath, size_t progressCallbackId) { + IUpdateSource* source = reinterpret_cast(userData); + VelopackAsset asset = to_cpp(*pAsset); + std::string localPath = to_cppstring(pszLocalPath); + std::function progress_callback = [progressCallbackId](size_t progress) { + vpkc_source_report_progress(progressCallbackId, progress); + }; + return source->DownloadReleaseEntry(asset, localPath, progress_callback); + }, + this); + } + virtual const std::string GetReleaseFeed(const std::string releasesName) = 0; + virtual bool DownloadReleaseEntry(const VelopackAsset& asset, const std::string localFilePath, vpkc_progress_send_t progress) = 0; +}; - /** @param Download the specified VelopackAsset to the provided local file path. - */ - virtual bool DownloadReleaseEntry(const char* assetFileName, const char* localFilePath) = 0; +/** + * A simple update source that reads release feeds and downloads assets from a local file path. + */ +class FileSource : public IUpdateSource { +public: + FileSource(const std::string& filePath) : IUpdateSource(vpkc_new_source_file(filePath.c_str())) { } +}; + +/** + * A simple update source that reads release feeds and downloads assets from an remote http url. + */ +class HttpSource : public IUpdateSource { +public: + HttpSource(const std::string& httpUrl) : IUpdateSource(vpkc_new_source_http_url(httpUrl.c_str())) { } }; /** @@ -362,7 +417,7 @@ private: public: /** * Create a new UpdateManager instance. - * @param urlOrPath Location of the update server or path to the local update directory. + * @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). */ @@ -388,36 +443,28 @@ public: /** * Create a new UpdateManager instance. - * @param pUpdateSource Custom update source implementation. + * @param updateSource 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). */ UpdateManager(std::unique_ptr pUpdateSource, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) { + vpkc_update_options_t vpkc_options; vpkc_update_options_t* pOptions = nullptr; if (options != nullptr) { - vpkc_update_options_t vpkc_options = to_c(*options); + vpkc_options = to_c(*options); pOptions = &vpkc_options; } - + + vpkc_locator_config_t vpkc_locator; vpkc_locator_config_t* pLocator = nullptr; if (locator != nullptr) { - vpkc_locator_config_t vpkc_locator = to_c(*locator); + 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)) { + vpkc_update_source_t* pSource = m_pUpdateSource->m_pSource; + if (!vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager)) { throw_last_error(); } }; @@ -514,7 +561,7 @@ public: * optionally restart your app. The updater will only wait for 60 seconds before giving up. */ void WaitExitThenApplyUpdate(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector restartArgs = {}) { - char** pRestartArgs = to_cstring_array(restartArgs); + char** pRestartArgs = allocate_cstring_array(restartArgs); vpkc_asset_t vpkc_asset = to_c(asset); bool result = vpkc_wait_exit_then_apply_update(m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size()); free_cstring_array(pRestartArgs, restartArgs.size()); diff --git a/src/lib-cpp/src/csource.rs b/src/lib-cpp/src/csource.rs index 91f528f5..a9b06240 100644 --- a/src/lib-cpp/src/csource.rs +++ b/src/lib-cpp/src/csource.rs @@ -28,6 +28,7 @@ pub fn report_csource_progress(callback_id: size_t, progress: i16) { pub struct CCallbackUpdateSource { pub p_user_data: *mut c_void, pub cb_get_release_feed: vpkc_release_feed_delegate_t, + pub cb_free_release_feed: vpkc_free_release_feed_t, pub cb_download_release_entry: vpkc_download_asset_delegate_t, } @@ -41,6 +42,7 @@ impl UpdateSource for CCallbackUpdateSource { let json_cstr_ptr = (self.cb_get_release_feed)(self.p_user_data, releases_name_cstr.as_ptr()); let json = c_to_string_opt(json_cstr_ptr) .ok_or(Error::Generic("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string()))?; + (self.cb_free_release_feed)(self.p_user_data, json_cstr_ptr); // Free the C string returned by the callback let feed: VelopackAssetFeed = serde_json::from_str(&json)?; Ok(feed) } diff --git a/src/lib-cpp/src/lib.rs b/src/lib-cpp/src/lib.rs index de9762b2..55242872 100644 --- a/src/lib-cpp/src/lib.rs +++ b/src/lib-cpp/src/lib.rs @@ -44,12 +44,14 @@ pub extern "C" fn vpkc_new_source_http_url(psz_http_url: *const c_char) -> *mut #[no_mangle] pub extern "C" fn vpkc_new_source_custom_callback( cb_release_feed: vpkc_release_feed_delegate_t, + cb_free_release_feed: vpkc_free_release_feed_t, cb_download_entry: vpkc_download_asset_delegate_t, p_user_data: *mut c_void, ) -> *mut vpkc_update_source_t { let cb_release_feed = cb_release_feed.to_option(); let cb_download_entry = cb_download_entry.to_option(); - if cb_release_feed.is_none() || cb_download_entry.is_none() { + let cb_free_release_feed = cb_free_release_feed.to_option(); + if cb_release_feed.is_none() || cb_download_entry.is_none() || cb_free_release_feed.is_none() { return ptr::null_mut(); } @@ -57,6 +59,7 @@ pub extern "C" fn vpkc_new_source_custom_callback( p_user_data, cb_get_release_feed: cb_release_feed.unwrap(), cb_download_release_entry: cb_download_entry.unwrap(), + cb_free_release_feed: cb_free_release_feed.unwrap(), }; UpdateSourceRawPtr::new(Box::new(source)) diff --git a/src/lib-cpp/src/raw.rs b/src/lib-cpp/src/raw.rs index 6c3a00e2..f9ae4e74 100644 --- a/src/lib-cpp/src/raw.rs +++ b/src/lib-cpp/src/raw.rs @@ -35,6 +35,12 @@ impl CallbackExt for vpkc_download_asset_delegate_t { } } +impl CallbackExt for vpkc_free_release_feed_t { + fn to_option(self) -> Option { + unsafe { std::mem::transmute::>(self) } + } +} + pub trait RawPtrExt<'a, T>: Sized { fn to_opaque_ref(self) -> Option<&'a T>; } diff --git a/src/lib-cpp/src/types.rs b/src/lib-cpp/src/types.rs index 138ab67d..ffdfe207 100644 --- a/src/lib-cpp/src/types.rs +++ b/src/lib-cpp/src/types.rs @@ -28,7 +28,10 @@ pub type vpkc_log_callback_t = extern "C" fn(p_user_data: *mut c_void, psz_level pub type vpkc_hook_callback_t = extern "C" fn(p_user_data: *mut c_void, psz_app_version: *const c_char); /// User delegate for to fetch a release feed. This function should return the raw JSON string of the release.json feed. -pub type vpkc_release_feed_delegate_t = extern "C" fn(p_user_data: *mut c_void, psz_releases_name: *const c_char) -> *const c_char; +pub type vpkc_release_feed_delegate_t = extern "C" fn(p_user_data: *mut c_void, psz_releases_name: *const c_char) -> *mut c_char; + +/// User delegate for freeing a release feed. This function should free the feed string returned by `vpkc_release_feed_delegate_t`. +pub type vpkc_free_release_feed_t = extern "C" fn(p_user_data: *mut c_void, psz_feed: *mut c_char); /// 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.