Add support for custom update sources in the C++ library

This commit is contained in:
Karthik Ravikanti
2024-12-12 12:04:54 +08:00
committed by Caelan
parent 8ac4f68467
commit 304eac3b71
5 changed files with 174 additions and 3 deletions

1
Cargo.lock generated
View File

@@ -2123,6 +2123,7 @@ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"log", "log",
"serde_json",
"velopack", "velopack",
] ]

View File

@@ -23,6 +23,7 @@ anyhow.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
log.workspace = true log.workspace = true
libc.workspace = true libc.workspace = true
serde_json.workspace = true
[build-dependencies] [build-dependencies]
cbindgen.workspace = true cbindgen.workspace = true

View File

@@ -175,6 +175,30 @@ bool vpkc_new_update_manager(const char *psz_url_or_path,
struct vpkc_locator_config_t *p_locator, struct vpkc_locator_config_t *p_locator,
vpkc_update_manager_t **p_manager); 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. * Returns the currently installed version of the app.
*/ */

View File

@@ -8,6 +8,7 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
#include <memory>
#include "Velopack.h" #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. * Provides functionality for checking for updates, downloading updates, and applying updates to the current application.
*/ */
class UpdateManager { class UpdateManager {
private: private:
vpkc_update_manager_t* m_pManager = 0; vpkc_update_manager_t* m_pManager = 0;
std::unique_ptr<IUpdateSource> m_pUpdateSource;
public: public:
/** /**
* Create a new UpdateManager instance. * 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<IUpdateSource> 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<IUpdateSource*>(userData);
return source->GetReleaseFeed(releasesName);
};
auto cbDownloadReleaseEntry = [](const char* assetFileName, const char* localFilePath, void* userData) {
IUpdateSource* source = reinterpret_cast<IUpdateSource*>(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. * Destructor for UpdateManager.
*/ */
@@ -480,4 +536,4 @@ public:
} // namespace Velopack } // namespace Velopack
#endif // VELOPACK_HPP #endif // VELOPACK_HPP

View File

@@ -10,8 +10,8 @@ use types::*;
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use libc::{c_char, c_void, size_t}; use libc::{c_char, c_void, size_t};
use std::ffi::CString; use std::{sync::mpsc::Sender, ffi::{CStr, CString}};
use velopack::{sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp}; use velopack::{bundle, sources, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp, VelopackAsset, VelopackAssetFeed};
/// Create a new UpdateManager instance. /// Create a new UpdateManager instance.
/// @param urlOrPath Location of the update server or path to the local update directory. /// @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<VelopackAssetFeed, VelopackError> {
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<Sender<i16>>) -> 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<dyn sources::UpdateSource> {
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. /// Returns the currently installed version of the app.
#[no_mangle] #[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 { 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 {