First version of handlebars is working

This commit is contained in:
Caelan Sayler
2025-05-14 08:33:39 +01:00
committed by Caelan
parent 96a191ed1b
commit 0dace00845
14 changed files with 676 additions and 237 deletions

View File

@@ -55,7 +55,7 @@ typedef struct vpkc_asset_t {
*/
char *Version;
/**
* The type of asset (eg. "Full" or "Delta").
* The type of asset (eg. "Full" or "Delta").
*/
char *Type;
/**
@@ -107,7 +107,7 @@ typedef struct vpkc_update_options_t {
*/
bool AllowVersionDowngrade;
/**
* **This option should usually be left None**. <br/>
* **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'.
@@ -116,6 +116,11 @@ typedef struct vpkc_update_options_t {
* 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;
/**
@@ -160,7 +165,19 @@ typedef struct vpkc_update_info_t {
/**
* The available version that we are updating to.
*/
struct vpkc_asset_t TargetFullRelease;
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
@@ -267,14 +284,14 @@ bool vpkc_is_portable(vpkc_update_manager_t *p_manager);
* Returns an UpdateInfo object if there is an update downloaded which still needs to be applied.
* You can pass the UpdateInfo object to waitExitThenApplyUpdate to apply the update.
*/
bool vpkc_update_pending_restart(vpkc_update_manager_t *p_manager, struct vpkc_asset_t *p_asset);
bool vpkc_update_pending_restart(vpkc_update_manager_t *p_manager, struct vpkc_asset_t **p_asset);
/**
* Checks for updates, returning None 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.
*/
vpkc_update_check_t vpkc_check_for_updates(vpkc_update_manager_t *p_manager,
struct vpkc_update_info_t *p_update);
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.

View File

@@ -91,6 +91,10 @@ struct VelopackAsset {
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<VelopackAsset> BaseRelease;
/// The list of delta updates that can be applied to the base version to get to the target version.
std::vector<VelopackAsset> 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.
@@ -104,7 +108,7 @@ struct UpdateOptions {
/// 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**. <br/>
/// **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'.
@@ -112,6 +116,9 @@ struct UpdateOptions {
/// 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<std::string> 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.
int64_t MaximumDeltasBeforeFallback;
};
static inline vpkc_locator_config_t to_c(const VelopackLocatorConfig& dto) {
@@ -167,6 +174,8 @@ static inline VelopackAsset to_cpp(const vpkc_asset_t& dto) {
static inline vpkc_update_info_t to_c(const UpdateInfo& dto) {
return {
to_c(dto.TargetFullRelease),
to_c_opt(dto.BaseRelease),
to_c(dto.DeltasToTarget),
to_cbool(dto.IsDowngrade),
};
}
@@ -174,6 +183,8 @@ static inline vpkc_update_info_t to_c(const UpdateInfo& dto) {
static inline UpdateInfo to_cpp(const vpkc_update_info_t& dto) {
return {
to_cpp(dto.TargetFullRelease),
to_cpp_opt(dto.BaseRelease),
to_cpp(dto.DeltasToTarget),
to_cppbool(dto.IsDowngrade),
};
}
@@ -182,6 +193,7 @@ static inline vpkc_update_options_t to_c(const UpdateOptions& dto) {
return {
to_cbool(dto.AllowVersionDowngrade),
to_cstring_opt(dto.ExplicitChannel),
to_ci32(dto.MaximumDeltasBeforeFallback),
};
}
@@ -189,6 +201,7 @@ static inline UpdateOptions to_cpp(const vpkc_update_options_t& dto) {
return {
to_cppbool(dto.AllowVersionDowngrade),
to_cppstring_opt(dto.ExplicitChannel),
to_cppi32(dto.MaximumDeltasBeforeFallback),
};
}
// !! AUTO-GENERATED-END CPP_TYPES

View File

@@ -42,9 +42,9 @@ impl UpdateSource for CCallbackUpdateSource {
if let Some(cb_get_release_feed) = self.cb_get_release_feed {
let json_cstr_ptr = (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()))?;
let json = c_to_String(json_cstr_ptr).map_err(|_| {
Error::Generic("User vpkc_release_feed_delegate_t returned a null pointer instead of an asset feed".to_string())
})?;
if let Some(cb_free_release_feed) = self.cb_free_release_feed {
(cb_free_release_feed)(self.p_user_data, json_cstr_ptr); // Free the C string returned by the callback
} else {
@@ -60,9 +60,7 @@ impl UpdateSource for CCallbackUpdateSource {
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
if let Some(cb_download_release_entry) = self.cb_download_release_entry {
let local_file_cstr = CString::new(local_file).unwrap();
let mut asset_c: vpkc_asset_t = unsafe { std::mem::zeroed() };
let asset_ptr: *mut vpkc_asset_t = &mut asset_c as *mut vpkc_asset_t;
unsafe { allocate_velopackasset(asset.clone(), asset_ptr) };
let asset_ptr = unsafe { allocate_VelopackAsset(asset) };
let progress_callback_id = PROGRESS_ID.fetch_add(1, Ordering::SeqCst);
if let Some(progress_sender) = &progress_sender {
@@ -71,8 +69,7 @@ impl UpdateSource for CCallbackUpdateSource {
}
let success = (cb_download_release_entry)(self.p_user_data, asset_ptr, local_file_cstr.as_ptr(), progress_callback_id);
unsafe { free_velopackasset(asset_ptr) };
unsafe { free_VelopackAsset(asset_ptr) };
if let Some(sender) = PROGRESS_CALLBACKS.write().unwrap().remove(&progress_callback_id) {
let _ = sender.send(100);

View File

@@ -15,16 +15,16 @@ use anyhow::{anyhow, bail};
use libc::{c_char, c_void, size_t};
use log_derive::{logfn, logfn_inputs};
use std::{ffi::CString, ptr};
use velopack::{sources, ApplyWaitMode, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
use velopack::locator::LocationContext;
use velopack::logging::{default_logfile_path, init_logging};
use velopack::{sources, ApplyWaitMode, Error as VelopackError, UpdateCheck, UpdateManager, VelopackApp};
/// Create a new FileSource update source for a given file path.
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_new_source_file(psz_file_path: *const c_char) -> *mut vpkc_update_source_t {
if let Some(update_path) = c_to_string_opt(psz_file_path) {
if let Some(update_path) = c_to_String(psz_file_path).ok() {
UpdateSourceRawPtr::new(Box::new(sources::FileSource::new(update_path)))
} else {
log::error!("psz_file_path is null");
@@ -37,7 +37,7 @@ pub extern "C" fn vpkc_new_source_file(psz_file_path: *const c_char) -> *mut vpk
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_new_source_http_url(psz_http_url: *const c_char) -> *mut vpkc_update_source_t {
if let Some(update_url) = c_to_string_opt(psz_http_url) {
if let Some(update_url) = c_to_String(psz_http_url).ok() {
UpdateSourceRawPtr::new(Box::new(sources::HttpSource::new(update_url)))
} else {
log::error!("psz_http_url is null");
@@ -103,10 +103,10 @@ pub extern "C" fn vpkc_new_update_manager(
p_manager: *mut *mut vpkc_update_manager_t,
) -> bool {
wrap_error(|| {
let update_url = c_to_string_opt(psz_url_or_path).ok_or(anyhow!("URL or path is null"))?;
let update_url = c_to_String(psz_url_or_path)?;
let source = sources::AutoSource::new(&update_url);
let options = c_to_updateoptions_opt(p_options);
let locator = c_to_velopacklocatorconfig_opt(p_locator);
let options = c_to_UpdateOptions(p_options).ok();
let locator = c_to_VelopackLocatorConfig(p_locator).ok();
let manager = UpdateManager::new(source, options, locator)?;
unsafe { *p_manager = UpdateManagerRawPtr::new(manager) };
Ok(())
@@ -128,8 +128,8 @@ pub extern "C" fn vpkc_new_update_manager_with_source(
) -> bool {
wrap_error(|| {
let source = UpdateSourceRawPtr::get_source_clone(p_source).ok_or(anyhow!("pSource must not be null"))?;
let options = c_to_updateoptions_opt(p_options);
let locator = c_to_velopacklocatorconfig_opt(p_locator);
let options = c_to_UpdateOptions(p_options).ok();
let locator = c_to_VelopackLocatorConfig(p_locator).ok();
let manager = UpdateManager::new_boxed(source, options, locator)?;
unsafe { *p_manager = UpdateManagerRawPtr::new(manager) };
Ok(())
@@ -181,11 +181,11 @@ pub extern "C" fn vpkc_is_portable(p_manager: *mut vpkc_update_manager_t) -> boo
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manager_t, p_asset: *mut vpkc_asset_t) -> bool {
pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manager_t, p_asset: *mut *mut vpkc_asset_t) -> bool {
match p_manager.to_opaque_ref() {
Some(manager) => match manager.get_update_pending_restart() {
Some(asset) => {
unsafe { allocate_velopackasset(asset, p_asset) };
unsafe { *p_asset = allocate_VelopackAsset(&asset) };
true
}
None => false,
@@ -199,13 +199,14 @@ pub extern "C" fn vpkc_update_pending_restart(p_manager: *mut vpkc_update_manage
#[no_mangle]
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_check_for_updates(p_manager: *mut vpkc_update_manager_t, p_update: *mut vpkc_update_info_t) -> vpkc_update_check_t {
pub extern "C" fn vpkc_check_for_updates(
p_manager: *mut vpkc_update_manager_t,
p_update: *mut *mut vpkc_update_info_t,
) -> vpkc_update_check_t {
match p_manager.to_opaque_ref() {
Some(manager) => match manager.check_for_updates() {
Ok(UpdateCheck::UpdateAvailable(info)) => {
unsafe {
allocate_updateinfo(info, p_update);
}
unsafe { *p_update = allocate_UpdateInfo(&info) };
vpkc_update_check_t::UPDATE_AVAILABLE
}
Ok(UpdateCheck::RemoteIsEmpty) => vpkc_update_check_t::REMOTE_IS_EMPTY,
@@ -243,7 +244,7 @@ pub extern "C" fn vpkc_download_updates(
None => bail!("pManager must not be null"),
};
let update = c_to_updateinfo_opt(p_update).ok_or(anyhow!("pUpdate must not be null"))?;
let update = c_to_UpdateInfo(p_update)?;
if let Some(cb_progress) = cb_progress {
let (progress_sender, progress_receiver) = std::sync::mpsc::channel::<i16>();
@@ -311,15 +312,15 @@ pub extern "C" fn vpkc_wait_exit_then_apply_updates(
None => bail!("pManager must not be null"),
};
let asset = c_to_velopackasset_opt(p_asset).ok_or(anyhow!("pAsset must not be null"))?;
let restart_args = c_to_string_array_opt(p_restart_args, c_restart_args).unwrap_or_default();
let asset = c_to_VelopackAsset(p_asset)?;
let restart_args = c_to_String_vec(p_restart_args, c_restart_args)?;
manager.wait_exit_then_apply_updates(&asset, b_silent, b_restart, &restart_args)?;
Ok(())
})
}
/// 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
/// 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).
#[no_mangle]
@@ -335,13 +336,9 @@ pub extern "C" fn vpkc_unsafe_apply_updates(
c_restart_args: size_t,
) -> bool {
wrap_error(|| {
let manager = match p_manager.to_opaque_ref() {
Some(manager) => manager,
None => bail!("pManager must not be null"),
};
let asset = c_to_velopackasset_opt(p_asset).ok_or(anyhow!("pAsset must not be null"))?;
let restart_args = c_to_string_array_opt(p_restart_args, c_restart_args).unwrap_or_default();
let manager = p_manager.to_opaque_ref().ok_or(anyhow!("pManager must not be null"))?;
let asset = c_to_VelopackAsset(p_asset)?;
let restart_args = c_to_String_vec(p_restart_args, c_restart_args)?;
let wait_mode = if dw_wait_pid > 0 { ApplyWaitMode::WaitPid(dw_wait_pid) } else { ApplyWaitMode::NoWait };
manager.unsafe_apply_updates(&asset, b_silent, wait_mode, b_restart, &restart_args)?;
Ok(())
@@ -361,7 +358,7 @@ pub extern "C" fn vpkc_free_update_manager(p_manager: *mut vpkc_update_manager_t
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_free_update_info(p_update_info: *mut vpkc_update_info_t) {
unsafe { free_updateinfo(p_update_info) };
unsafe { free_UpdateInfo(p_update_info) };
}
/// Frees a vpkc_asset_t instance.
@@ -369,7 +366,7 @@ pub extern "C" fn vpkc_free_update_info(p_update_info: *mut vpkc_update_info_t)
#[logfn(Trace)]
#[logfn_inputs(Trace)]
pub extern "C" fn vpkc_free_asset(p_asset: *mut vpkc_asset_t) {
unsafe { free_velopackasset(p_asset) };
unsafe { free_VelopackAsset(p_asset) };
}
/// VelopackApp helps you to handle app activation events correctly.
@@ -439,14 +436,14 @@ pub extern "C" fn vpkc_app_run(p_user_data: *mut c_void) {
hook(p_user_data, c_string.as_ptr());
});
}
// init logging
let log_file = if let Some(locator) = &app_options.locator {
default_logfile_path(locator)
} else {
default_logfile_path(LocationContext::FromCurrentExe)
};
init_logging("lib-cpp", Some(&log_file), false, false, Some(create_shared_logger()));
app.run();
}
@@ -463,7 +460,7 @@ pub extern "C" fn vpkc_app_set_auto_apply_on_startup(b_auto_apply: bool) {
#[no_mangle]
pub extern "C" fn vpkc_app_set_args(p_args: *mut *mut c_char, c_args: size_t) {
update_app_options(|opt| {
opt.args = c_to_string_array_opt(p_args, c_args);
opt.args = c_to_String_vec(p_args, c_args).ok();
});
}
@@ -471,7 +468,7 @@ pub extern "C" fn vpkc_app_set_args(p_args: *mut *mut c_char, c_args: size_t) {
#[no_mangle]
pub extern "C" fn vpkc_app_set_locator(p_locator: *mut vpkc_locator_config_t) {
update_app_options(|opt| {
opt.locator = c_to_velopacklocatorconfig_opt(p_locator);
opt.locator = c_to_VelopackLocatorConfig(p_locator).ok();
});
}

View File

@@ -1,3 +1,4 @@
use anyhow::{bail, Result};
use libc::{c_char, c_void, size_t};
use std::ffi::{CStr, CString};
use std::path::PathBuf;
@@ -47,73 +48,52 @@ pub type vpkc_download_asset_delegate_t = Option<
) -> bool,
>;
pub fn c_to_string_opt(psz: *const c_char) -> Option<String> {
pub fn c_to_String(psz: *const c_char) -> Result<String> {
if psz.is_null() {
return None;
bail!("Null pointer: String must be set.");
}
let cstr = unsafe { CStr::from_ptr(psz) };
Some(String::from_utf8_lossy(cstr.to_bytes()).to_string())
Ok(String::from_utf8_lossy(cstr.to_bytes()).to_string())
}
pub fn c_to_string(psz: *const c_char) -> String {
c_to_string_opt(psz).unwrap_or_default()
pub fn c_to_String_vec(p_args: *mut *mut c_char, c_args: size_t) -> Result<Vec<String>> {
if p_args.is_null() || c_args == 0 {
return Ok(Vec::new());
}
let mut args = Vec::with_capacity(c_args);
for i in 0..c_args {
let arg = c_to_String(unsafe { *p_args.add(i) })?;
args.push(arg);
}
Ok(args)
}
pub fn c_to_pathbuf(psz: *const c_char) -> PathBuf {
PathBuf::from(c_to_string(psz))
pub fn c_to_PathBuf(psz: *const c_char) -> Result<PathBuf> {
c_to_String(psz).map(PathBuf::from)
}
pub fn string_to_cstr(s: &str) -> *mut c_char {
let cstr = CString::new(s).unwrap();
pub fn allocate_String<'a, T: Into<Option<&'a String>>>(s: T) -> *mut c_char {
let s = s.into();
if s.is_none() {
return std::ptr::null_mut();
}
let s = s.unwrap();
let cstr = CString::new(s.clone()).unwrap();
cstr.into_raw()
}
pub fn free_cstr(psz: *mut c_char) {
pub fn allocate_PathBuf(p: &PathBuf) -> *mut c_char {
let st = p.to_string_lossy().to_string();
allocate_String(&st)
}
pub unsafe fn free_String(psz: *mut c_char) {
if !psz.is_null() {
let _ = unsafe { CString::from_raw(psz) };
}
}
pub fn allocate_string(s: String, psz: *mut *mut c_char) {
if psz.is_null() {
return;
}
unsafe { *psz = string_to_cstr(&s) };
}
pub fn allocate_string_opt(s: Option<String>, psz: *mut *mut c_char) {
if let Some(s) = s {
allocate_string(s, psz);
}
}
pub unsafe fn free_string(psz: *mut *mut c_char) {
if !psz.is_null() {
free_cstr(*psz);
}
}
pub fn allocate_pathbuf(p: PathBuf, psz: *mut *mut c_char) {
allocate_string(p.to_string_lossy().to_string(), psz);
}
pub unsafe fn free_pathbuf(psz: *mut *mut c_char) {
free_string(psz);
}
pub fn c_to_string_array_opt(p_args: *mut *mut c_char, c_args: size_t) -> Option<Vec<String>> {
if p_args.is_null() || c_args == 0 {
return None;
}
let mut args = Vec::with_capacity(c_args);
for i in 0..c_args {
if let Some(arg) = c_to_string_opt(unsafe { *p_args.add(i) }) {
args.push(arg);
}
}
Some(args)
pub unsafe fn free_PathBuf(psz: *mut c_char) {
free_String(psz);
}
pub fn return_cstr(psz: *mut c_char, c: size_t, s: &str) -> size_t {
@@ -150,44 +130,89 @@ pub struct vpkc_locator_config_t {
}
#[rustfmt::skip]
pub fn c_to_velopacklocatorconfig(obj: &vpkc_locator_config_t) -> VelopackLocatorConfig {
VelopackLocatorConfig {
RootAppDir: c_to_pathbuf(obj.RootAppDir),
UpdateExePath: c_to_pathbuf(obj.UpdateExePath),
PackagesDir: c_to_pathbuf(obj.PackagesDir),
ManifestPath: c_to_pathbuf(obj.ManifestPath),
CurrentBinaryDir: c_to_pathbuf(obj.CurrentBinaryDir),
pub fn c_to_VelopackLocatorConfig(obj: *mut vpkc_locator_config_t) -> Result<VelopackLocatorConfig> {
if obj.is_null() { bail!("Null pointer: VelopackLocatorConfig must be set."); }
let obj = unsafe { &*obj };
let result = VelopackLocatorConfig {
RootAppDir: c_to_PathBuf(obj.RootAppDir)?,
UpdateExePath: c_to_PathBuf(obj.UpdateExePath)?,
PackagesDir: c_to_PathBuf(obj.PackagesDir)?,
ManifestPath: c_to_PathBuf(obj.ManifestPath)?,
CurrentBinaryDir: c_to_PathBuf(obj.CurrentBinaryDir)?,
IsPortable: obj.IsPortable,
};
Ok(result)
}
#[rustfmt::skip]
pub fn c_to_VelopackLocatorConfig_vec(obj: *mut *mut vpkc_locator_config_t, count: size_t) -> Result<Vec<VelopackLocatorConfig>> {
if obj.is_null() || count == 0 { return Ok(Vec::new()); }
let mut assets = Vec::with_capacity(count as usize);
for i in 0..count {
let ptr = unsafe { *obj.add(i as usize) };
assets.push(c_to_VelopackLocatorConfig(ptr)?);
}
Ok(assets)
}
#[rustfmt::skip]
pub fn c_to_velopacklocatorconfig_opt(obj: *mut vpkc_locator_config_t) -> Option<VelopackLocatorConfig> {
if obj.is_null() { return None; }
Some(c_to_velopacklocatorconfig(unsafe { &*obj }))
}
#[rustfmt::skip]
pub unsafe fn allocate_velopacklocatorconfig(dto: VelopackLocatorConfig, obj: *mut vpkc_locator_config_t) {
if obj.is_null() { return; }
pub unsafe fn allocate_VelopackLocatorConfig<'a, T: Into<Option<&'a VelopackLocatorConfig>>>(dto: T) -> *mut vpkc_locator_config_t {
let dto = dto.into();
if dto.is_none() {
return std::ptr::null_mut();
}
log::debug!("vpkc_locator_config_t allocated");
allocate_pathbuf(dto.RootAppDir, &mut (*obj).RootAppDir);
allocate_pathbuf(dto.UpdateExePath, &mut (*obj).UpdateExePath);
allocate_pathbuf(dto.PackagesDir, &mut (*obj).PackagesDir);
allocate_pathbuf(dto.ManifestPath, &mut (*obj).ManifestPath);
allocate_pathbuf(dto.CurrentBinaryDir, &mut (*obj).CurrentBinaryDir);
let dto = dto.unwrap();
let obj = libc::malloc(size_of::<vpkc_locator_config_t>()) as *mut vpkc_locator_config_t;
(*obj).RootAppDir = allocate_PathBuf(&dto.RootAppDir);
(*obj).UpdateExePath = allocate_PathBuf(&dto.UpdateExePath);
(*obj).PackagesDir = allocate_PathBuf(&dto.PackagesDir);
(*obj).ManifestPath = allocate_PathBuf(&dto.ManifestPath);
(*obj).CurrentBinaryDir = allocate_PathBuf(&dto.CurrentBinaryDir);
(*obj).IsPortable = dto.IsPortable;
obj
}
#[rustfmt::skip]
pub unsafe fn free_velopacklocatorconfig(obj: *mut vpkc_locator_config_t) {
pub unsafe fn allocate_VelopackLocatorConfig_vec(dto: &Vec<VelopackLocatorConfig>, count: *mut size_t) -> *mut *mut vpkc_locator_config_t {
if dto.is_empty() {
*count = 0;
return std::ptr::null_mut();
}
log::debug!("vpkc_locator_config_t vector allocated");
let count_value = dto.len() as size_t;
let mut assets = Vec::with_capacity(count_value as usize);
for i in 0..count_value {
let ptr = allocate_VelopackLocatorConfig(&dto[i as usize]);
assets.push(ptr);
}
let ptr = assets.as_mut_ptr();
std::mem::forget(assets);
ptr
}
#[rustfmt::skip]
pub unsafe fn free_VelopackLocatorConfig(obj: *mut vpkc_locator_config_t) {
if obj.is_null() { return; }
free_PathBuf((*obj).RootAppDir);
free_PathBuf((*obj).UpdateExePath);
free_PathBuf((*obj).PackagesDir);
free_PathBuf((*obj).ManifestPath);
free_PathBuf((*obj).CurrentBinaryDir);
libc::free(obj as *mut c_void);
log::debug!("vpkc_locator_config_t freed");
free_pathbuf(&mut (*obj).RootAppDir);
free_pathbuf(&mut (*obj).UpdateExePath);
free_pathbuf(&mut (*obj).PackagesDir);
free_pathbuf(&mut (*obj).ManifestPath);
free_pathbuf(&mut (*obj).CurrentBinaryDir);
}
#[rustfmt::skip]
pub unsafe fn free_VelopackLocatorConfig_vec(obj: *mut *mut vpkc_locator_config_t, count: size_t) {
if obj.is_null() || count == 0 { return; }
let vec = Vec::from_raw_parts(obj, count as usize, count as usize);
for i in 0..count {
let ptr = *vec.get_unchecked(i as usize);
free_VelopackLocatorConfig(ptr);
}
log::debug!("vpkc_locator_config_t vector freed");
}
#[rustfmt::skip]
@@ -198,7 +223,7 @@ pub struct vpkc_asset_t {
pub PackageId: *mut c_char,
/// The version of this release.
pub Version: *mut c_char,
/// The type of asset (eg. "Full" or "Delta").
/// The type of asset (eg. &quot;Full&quot; or &quot;Delta&quot;).
pub Type: *mut c_char,
/// The filename of the update package containing this release.
pub FileName: *mut c_char,
@@ -215,53 +240,98 @@ pub struct vpkc_asset_t {
}
#[rustfmt::skip]
pub fn c_to_velopackasset(obj: &vpkc_asset_t) -> VelopackAsset {
VelopackAsset {
PackageId: c_to_string(obj.PackageId),
Version: c_to_string(obj.Version),
Type: c_to_string(obj.Type),
FileName: c_to_string(obj.FileName),
SHA1: c_to_string(obj.SHA1),
SHA256: c_to_string(obj.SHA256),
pub fn c_to_VelopackAsset(obj: *mut vpkc_asset_t) -> Result<VelopackAsset> {
if obj.is_null() { bail!("Null pointer: VelopackAsset must be set."); }
let obj = unsafe { &*obj };
let result = VelopackAsset {
PackageId: c_to_String(obj.PackageId)?,
Version: c_to_String(obj.Version)?,
Type: c_to_String(obj.Type)?,
FileName: c_to_String(obj.FileName)?,
SHA1: c_to_String(obj.SHA1)?,
SHA256: c_to_String(obj.SHA256)?,
Size: obj.Size,
NotesMarkdown: c_to_string(obj.NotesMarkdown),
NotesHtml: c_to_string(obj.NotesHtml),
NotesMarkdown: c_to_String(obj.NotesMarkdown)?,
NotesHtml: c_to_String(obj.NotesHtml)?,
};
Ok(result)
}
#[rustfmt::skip]
pub fn c_to_VelopackAsset_vec(obj: *mut *mut vpkc_asset_t, count: size_t) -> Result<Vec<VelopackAsset>> {
if obj.is_null() || count == 0 { return Ok(Vec::new()); }
let mut assets = Vec::with_capacity(count as usize);
for i in 0..count {
let ptr = unsafe { *obj.add(i as usize) };
assets.push(c_to_VelopackAsset(ptr)?);
}
Ok(assets)
}
#[rustfmt::skip]
pub fn c_to_velopackasset_opt(obj: *mut vpkc_asset_t) -> Option<VelopackAsset> {
if obj.is_null() { return None; }
Some(c_to_velopackasset(unsafe { &*obj }))
}
#[rustfmt::skip]
pub unsafe fn allocate_velopackasset(dto: VelopackAsset, obj: *mut vpkc_asset_t) {
if obj.is_null() { return; }
pub unsafe fn allocate_VelopackAsset<'a, T: Into<Option<&'a VelopackAsset>>>(dto: T) -> *mut vpkc_asset_t {
let dto = dto.into();
if dto.is_none() {
return std::ptr::null_mut();
}
log::debug!("vpkc_asset_t allocated");
allocate_string(dto.PackageId, &mut (*obj).PackageId);
allocate_string(dto.Version, &mut (*obj).Version);
allocate_string(dto.Type, &mut (*obj).Type);
allocate_string(dto.FileName, &mut (*obj).FileName);
allocate_string(dto.SHA1, &mut (*obj).SHA1);
allocate_string(dto.SHA256, &mut (*obj).SHA256);
let dto = dto.unwrap();
let obj = libc::malloc(size_of::<vpkc_asset_t>()) as *mut vpkc_asset_t;
(*obj).PackageId = allocate_String(&dto.PackageId);
(*obj).Version = allocate_String(&dto.Version);
(*obj).Type = allocate_String(&dto.Type);
(*obj).FileName = allocate_String(&dto.FileName);
(*obj).SHA1 = allocate_String(&dto.SHA1);
(*obj).SHA256 = allocate_String(&dto.SHA256);
(*obj).Size = dto.Size;
allocate_string(dto.NotesMarkdown, &mut (*obj).NotesMarkdown);
allocate_string(dto.NotesHtml, &mut (*obj).NotesHtml);
(*obj).NotesMarkdown = allocate_String(&dto.NotesMarkdown);
(*obj).NotesHtml = allocate_String(&dto.NotesHtml);
obj
}
#[rustfmt::skip]
pub unsafe fn free_velopackasset(obj: *mut vpkc_asset_t) {
pub unsafe fn allocate_VelopackAsset_vec(dto: &Vec<VelopackAsset>, count: *mut size_t) -> *mut *mut vpkc_asset_t {
if dto.is_empty() {
*count = 0;
return std::ptr::null_mut();
}
log::debug!("vpkc_asset_t vector allocated");
let count_value = dto.len() as size_t;
let mut assets = Vec::with_capacity(count_value as usize);
for i in 0..count_value {
let ptr = allocate_VelopackAsset(&dto[i as usize]);
assets.push(ptr);
}
let ptr = assets.as_mut_ptr();
std::mem::forget(assets);
ptr
}
#[rustfmt::skip]
pub unsafe fn free_VelopackAsset(obj: *mut vpkc_asset_t) {
if obj.is_null() { return; }
free_String((*obj).PackageId);
free_String((*obj).Version);
free_String((*obj).Type);
free_String((*obj).FileName);
free_String((*obj).SHA1);
free_String((*obj).SHA256);
free_String((*obj).NotesMarkdown);
free_String((*obj).NotesHtml);
libc::free(obj as *mut c_void);
log::debug!("vpkc_asset_t freed");
free_string(&mut (*obj).PackageId);
free_string(&mut (*obj).Version);
free_string(&mut (*obj).Type);
free_string(&mut (*obj).FileName);
free_string(&mut (*obj).SHA1);
free_string(&mut (*obj).SHA256);
free_string(&mut (*obj).NotesMarkdown);
free_string(&mut (*obj).NotesHtml);
}
#[rustfmt::skip]
pub unsafe fn free_VelopackAsset_vec(obj: *mut *mut vpkc_asset_t, count: size_t) {
if obj.is_null() || count == 0 { return; }
let vec = Vec::from_raw_parts(obj, count as usize, count as usize);
for i in 0..count {
let ptr = *vec.get_unchecked(i as usize);
free_VelopackAsset(ptr);
}
log::debug!("vpkc_asset_t vector freed");
}
#[rustfmt::skip]
@@ -269,40 +339,97 @@ pub unsafe fn free_velopackasset(obj: *mut vpkc_asset_t) {
/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
pub struct vpkc_update_info_t {
/// The available version that we are updating to.
pub TargetFullRelease: vpkc_asset_t,
pub TargetFullRelease: *mut vpkc_asset_t,
/// The base release that this update is based on. This is only available if the update is a delta update.
pub BaseRelease: *mut vpkc_asset_t,
/// The list of delta updates that can be applied to the base version to get to the target version.
pub DeltasToTarget: *mut *mut vpkc_asset_t,
/// The number of elements in the DeltasToTarget array.
pub DeltasToTargetCount: size_t,
/// 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.
/// In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
/// deleted.
pub IsDowngrade: bool,
}
#[rustfmt::skip]
pub fn c_to_updateinfo(obj: &vpkc_update_info_t) -> UpdateInfo {
UpdateInfo {
TargetFullRelease: c_to_velopackasset(&obj.TargetFullRelease),
pub fn c_to_UpdateInfo(obj: *mut vpkc_update_info_t) -> Result<UpdateInfo> {
if obj.is_null() { bail!("Null pointer: UpdateInfo must be set."); }
let obj = unsafe { &*obj };
let result = UpdateInfo {
TargetFullRelease: c_to_VelopackAsset(obj.TargetFullRelease)?,
BaseRelease: c_to_VelopackAsset(obj.BaseRelease).ok(),
DeltasToTarget: c_to_VelopackAsset_vec(obj.DeltasToTarget, obj.DeltasToTargetCount)?,
IsDowngrade: obj.IsDowngrade,
};
Ok(result)
}
#[rustfmt::skip]
pub fn c_to_UpdateInfo_vec(obj: *mut *mut vpkc_update_info_t, count: size_t) -> Result<Vec<UpdateInfo>> {
if obj.is_null() || count == 0 { return Ok(Vec::new()); }
let mut assets = Vec::with_capacity(count as usize);
for i in 0..count {
let ptr = unsafe { *obj.add(i as usize) };
assets.push(c_to_UpdateInfo(ptr)?);
}
Ok(assets)
}
#[rustfmt::skip]
pub fn c_to_updateinfo_opt(obj: *mut vpkc_update_info_t) -> Option<UpdateInfo> {
if obj.is_null() { return None; }
Some(c_to_updateinfo(unsafe { &*obj }))
}
#[rustfmt::skip]
pub unsafe fn allocate_updateinfo(dto: UpdateInfo, obj: *mut vpkc_update_info_t) {
if obj.is_null() { return; }
pub unsafe fn allocate_UpdateInfo<'a, T: Into<Option<&'a UpdateInfo>>>(dto: T) -> *mut vpkc_update_info_t {
let dto = dto.into();
if dto.is_none() {
return std::ptr::null_mut();
}
log::debug!("vpkc_update_info_t allocated");
allocate_velopackasset(dto.TargetFullRelease, &mut (*obj).TargetFullRelease);
let dto = dto.unwrap();
let obj = libc::malloc(size_of::<vpkc_update_info_t>()) as *mut vpkc_update_info_t;
(*obj).TargetFullRelease = allocate_VelopackAsset(&dto.TargetFullRelease);
(*obj).BaseRelease = allocate_VelopackAsset(&dto.BaseRelease);
(*obj).DeltasToTarget = allocate_VelopackAsset_vec(&dto.DeltasToTarget, &mut (*obj).DeltasToTargetCount);
(*obj).IsDowngrade = dto.IsDowngrade;
obj
}
#[rustfmt::skip]
pub unsafe fn free_updateinfo(obj: *mut vpkc_update_info_t) {
pub unsafe fn allocate_UpdateInfo_vec(dto: &Vec<UpdateInfo>, count: *mut size_t) -> *mut *mut vpkc_update_info_t {
if dto.is_empty() {
*count = 0;
return std::ptr::null_mut();
}
log::debug!("vpkc_update_info_t vector allocated");
let count_value = dto.len() as size_t;
let mut assets = Vec::with_capacity(count_value as usize);
for i in 0..count_value {
let ptr = allocate_UpdateInfo(&dto[i as usize]);
assets.push(ptr);
}
let ptr = assets.as_mut_ptr();
std::mem::forget(assets);
ptr
}
#[rustfmt::skip]
pub unsafe fn free_UpdateInfo(obj: *mut vpkc_update_info_t) {
if obj.is_null() { return; }
free_VelopackAsset((*obj).TargetFullRelease);
free_VelopackAsset((*obj).BaseRelease);
free_VelopackAsset_vec((*obj).DeltasToTarget, (*obj).DeltasToTargetCount);
libc::free(obj as *mut c_void);
log::debug!("vpkc_update_info_t freed");
free_velopackasset(&mut (*obj).TargetFullRelease);
}
#[rustfmt::skip]
pub unsafe fn free_UpdateInfo_vec(obj: *mut *mut vpkc_update_info_t, count: size_t) {
if obj.is_null() || count == 0 { return; }
let vec = Vec::from_raw_parts(obj, count as usize, count as usize);
for i in 0..count {
let ptr = *vec.get_unchecked(i as usize);
free_UpdateInfo(ptr);
}
log::debug!("vpkc_update_info_t vector freed");
}
#[rustfmt::skip]
@@ -310,46 +437,97 @@ pub unsafe fn free_updateinfo(obj: *mut vpkc_update_info_t) {
/// Options to customise the behaviour of UpdateManager.
pub 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.
/// 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.
pub AllowVersionDowngrade: bool,
/// **This option should usually be left None**. <br/>
/// 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.
/// **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.
pub ExplicitChannel: *mut c_char,
/// 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.
pub MaximumDeltasBeforeFallback: i32,
}
#[rustfmt::skip]
pub fn c_to_updateoptions(obj: &vpkc_update_options_t) -> UpdateOptions {
UpdateOptions {
pub fn c_to_UpdateOptions(obj: *mut vpkc_update_options_t) -> Result<UpdateOptions> {
if obj.is_null() { bail!("Null pointer: UpdateOptions must be set."); }
let obj = unsafe { &*obj };
let result = UpdateOptions {
AllowVersionDowngrade: obj.AllowVersionDowngrade,
ExplicitChannel: c_to_string_opt(obj.ExplicitChannel),
ExplicitChannel: c_to_String(obj.ExplicitChannel).ok(),
MaximumDeltasBeforeFallback: obj.MaximumDeltasBeforeFallback,
};
Ok(result)
}
#[rustfmt::skip]
pub fn c_to_UpdateOptions_vec(obj: *mut *mut vpkc_update_options_t, count: size_t) -> Result<Vec<UpdateOptions>> {
if obj.is_null() || count == 0 { return Ok(Vec::new()); }
let mut assets = Vec::with_capacity(count as usize);
for i in 0..count {
let ptr = unsafe { *obj.add(i as usize) };
assets.push(c_to_UpdateOptions(ptr)?);
}
Ok(assets)
}
#[rustfmt::skip]
pub fn c_to_updateoptions_opt(obj: *mut vpkc_update_options_t) -> Option<UpdateOptions> {
if obj.is_null() { return None; }
Some(c_to_updateoptions(unsafe { &*obj }))
}
#[rustfmt::skip]
pub unsafe fn allocate_updateoptions(dto: UpdateOptions, obj: *mut vpkc_update_options_t) {
if obj.is_null() { return; }
pub unsafe fn allocate_UpdateOptions<'a, T: Into<Option<&'a UpdateOptions>>>(dto: T) -> *mut vpkc_update_options_t {
let dto = dto.into();
if dto.is_none() {
return std::ptr::null_mut();
}
log::debug!("vpkc_update_options_t allocated");
let dto = dto.unwrap();
let obj = libc::malloc(size_of::<vpkc_update_options_t>()) as *mut vpkc_update_options_t;
(*obj).AllowVersionDowngrade = dto.AllowVersionDowngrade;
allocate_string_opt(dto.ExplicitChannel, &mut (*obj).ExplicitChannel);
(*obj).ExplicitChannel = allocate_String(&dto.ExplicitChannel);
(*obj).MaximumDeltasBeforeFallback = dto.MaximumDeltasBeforeFallback;
obj
}
#[rustfmt::skip]
pub unsafe fn free_updateoptions(obj: *mut vpkc_update_options_t) {
pub unsafe fn allocate_UpdateOptions_vec(dto: &Vec<UpdateOptions>, count: *mut size_t) -> *mut *mut vpkc_update_options_t {
if dto.is_empty() {
*count = 0;
return std::ptr::null_mut();
}
log::debug!("vpkc_update_options_t vector allocated");
let count_value = dto.len() as size_t;
let mut assets = Vec::with_capacity(count_value as usize);
for i in 0..count_value {
let ptr = allocate_UpdateOptions(&dto[i as usize]);
assets.push(ptr);
}
let ptr = assets.as_mut_ptr();
std::mem::forget(assets);
ptr
}
#[rustfmt::skip]
pub unsafe fn free_UpdateOptions(obj: *mut vpkc_update_options_t) {
if obj.is_null() { return; }
free_String((*obj).ExplicitChannel);
libc::free(obj as *mut c_void);
log::debug!("vpkc_update_options_t freed");
free_string(&mut (*obj).ExplicitChannel);
}
#[rustfmt::skip]
pub unsafe fn free_UpdateOptions_vec(obj: *mut *mut vpkc_update_options_t, count: size_t) {
if obj.is_null() || count == 0 { return; }
let vec = Vec::from_raw_parts(obj, count as usize, count as usize);
for i in 0..count {
let ptr = *vec.get_unchecked(i as usize);
free_UpdateOptions(ptr);
}
log::debug!("vpkc_update_options_t vector freed");
}
// !! AUTO-GENERATED-END RUST_TYPES

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using HandlebarsDotNet;
var scriptsDir = Assembly.GetEntryAssembly()!
.GetCustomAttributes<AssemblyMetadataAttribute>()
@@ -6,6 +7,7 @@ var scriptsDir = Assembly.GetEntryAssembly()!
var librustDir = Path.Combine(scriptsDir, "..", "..", "lib-rust", "src");
var libcppDir = Path.Combine(scriptsDir, "..");
var templatesDir = Path.Combine(scriptsDir, "Templates");
var files = Directory.EnumerateFiles(librustDir, "*.rs", SearchOption.AllDirectories);
string[] desiredStructs = [
@@ -83,11 +85,52 @@ foreach (var rs in availableStructs) {
}
Console.WriteLine("Generating Rust-C types");
var rustCTypes = new IndentStringBuilder();
foreach (var rs in availableStructs) {
Templates.WriteRustCRepr(basic_libc_names, rustCTypes, rs);
}
//var rustCTypes = new IndentStringBuilder();
//foreach (var rs in availableStructs) {
// Templates.WriteRustCRepr(basic_libc_names, rustCTypes, rs);
//}
var rustCTypesTemplate = Handlebars.Compile(File.ReadAllText(Path.Combine(templatesDir, "rust_struct.hbs")));
var types = new List<TypeMap>() {
new TypeMap("VelopackAsset", "vpkc_asset_t", "vpkc_asset_t", false),
new TypeMap("UpdateInfo", "vpkc_update_info_t", "vpkc_update_info_t", false),
new TypeMap("UpdateOptions", "vpkc_update_options_t", "vpkc_update_options_t", false),
new TypeMap("VelopackLocatorConfig", "vpkc_locator_config_t", "vpkc_locator_config_t", false),
new TypeMap("String", "char", "c_char", false),
new TypeMap("PathBuf", "char", "c_char", false),
new TypeMap("bool", "bool", "bool", true),
new TypeMap("i32", "int32_t", "i32", true),
new TypeMap("i64", "int64_t", "i64", true),
new TypeMap("u32", "uint32_t", "u32", true),
new TypeMap("u64", "uint64_t", "u64", true),
}.ToDictionary(v => v.rustType, v => v);
var data = availableStructs.Select(s => new RustStruct_Struct {
rust_comment = s.DocComment.PrefixEveryLine("/// "),
struct_rust_name = s.Name,
struct_c_name = types[s.Name].interopType,
fields = s.Fields.Select(f => {
var isString = types[f.Type].rustType == "PathBuf" || types[f.Type].rustType == "String";
var field = new RustStruct_Field {
rust_comment = f.DocComment.PrefixEveryLine("/// "),
field_name = f.Name,
field_optional = f.Optional,
field_vector = f.Vec,
//field_add_pointer = f.Optional && !f.Vec && !isString,
//field_requires_ref = !isString && !f.Vec && !f.Optional,
//field_string = isString,
field_rust_type = f.Type,
field_c_type = types[f.Type].interopType,
field_primitive = types[f.Type].primitive,
field_normal = !f.Vec && !types[f.Type].primitive,
};
return field;
}).ToArray(),
}).ToArray();
var rustCTypes = rustCTypesTemplate(data);
Console.WriteLine();
//Console.WriteLine("Generating C to bridge mappings");
//var cToBridgeMapping = new IndentStringBuilder();
//foreach (var rs in availableStructs) {
@@ -102,4 +145,30 @@ Util.ReplaceTextInFile(rustTypes, "RUST_TYPES", rustCTypes.ToString());
Util.ReplaceTextInFile(rustCppInclude, "CPP_TYPES", cppTypes.ToString());
//Util.ReplaceTextInFile(rustBridgeC, "BRIDGE_MAPPING", cToBridgeMapping.ToString());
return 0;
return 0;
record struct TypeMap(string rustType, string cType, string interopType, bool primitive);
class RustStruct_Struct
{
public string struct_c_name;
public string struct_rust_name;
public string rust_comment;
public RustStruct_Field[] fields;
}
class RustStruct_Field
{
public string field_name;
public string field_c_type;
public string field_rust_type;
public bool field_primitive;
public bool field_optional;
public bool field_vector;
//public bool field_prefix;
//public bool field_add_pointer;
//public bool field_requires_ref;
public bool field_normal;
//public bool field_string;
public string rust_comment;
}

View File

@@ -1,7 +1,7 @@
using System.Text.RegularExpressions;
using Superpower;
using Superpower.Parsers;
using Superpower.Model;
using Superpower.Parsers;
using Superpower.Tokenizers;
public class RustField
@@ -10,6 +10,7 @@ public class RustField
public string Name { get; set; }
public string Type { get; set; }
public bool Optional { get; set; }
public bool Vec { get; set; }
}
public class RustStruct
@@ -80,7 +81,7 @@ public static class StructParser
{
return
(from nested in SkipNestedBraces() select Unit.Value) // handle recursive braces
.Or(from nonBrace in Token.Matching<RustToken>(kind => kind != RustToken.OpenBrace && kind != RustToken.CloseBrace,"non-brace").AtLeastOnce() select Unit.Value).Many()
.Or(from nonBrace in Token.Matching<RustToken>(kind => kind != RustToken.OpenBrace && kind != RustToken.CloseBrace, "non-brace").AtLeastOnce() select Unit.Value).Many()
.Select(_ => Unit.Value);
}
@@ -92,7 +93,7 @@ public static class StructParser
private static readonly TokenListParser<RustToken, string> TypeParser =
from rest in Token.Matching<RustToken>(kind => kind != RustToken.Comma, "Expected tokens before ','").AtLeastOnce()
from end in Token.EqualTo(RustToken.Comma)
from end in Token.EqualTo(RustToken.Comma)
select string.Join(" ", rest.Select(t => t.ToStringValue()));
private static readonly TokenListParser<RustToken, RustField> FieldDefinition =
@@ -103,8 +104,7 @@ public static class StructParser
from fieldName in Token.EqualTo(RustToken.Identifier).Select(t => t.ToStringValue())
from colon in Token.EqualTo(RustToken.Colon)
from fieldType in TypeParser
select new RustField
{
select new RustField {
DocComment = docComments,
Name = fieldName,
Type = fieldType.Trim()
@@ -124,8 +124,7 @@ public static class StructParser
from structKeyword in Token.EqualTo(RustToken.KeywordStruct)
from structName in Token.EqualTo(RustToken.Identifier).Select(t => t.ToStringValue())
from structBody in StructBody
select new RustStruct
{
select new RustStruct {
DocComment = docComments,
Name = structName,
Fields = structBody
@@ -133,7 +132,7 @@ public static class StructParser
private static readonly TokenListParser<RustToken, RustStruct> TopLevelItem =
(from impl in ImplBlock
select (RustStruct)null)
select (RustStruct) null)
.Or(
from structDef in StructDefinition
select structDef
@@ -145,27 +144,30 @@ public static class StructParser
var parser = TopLevelItem.Many();
var result = parser(tokens);
if (!result.HasValue)
{
if (!result.HasValue) {
throw new Exception(result.ToString());
}
var structs = result.Value.Where(s => s != null).ToArray();
foreach(var s in structs)
{
foreach(var f in s.Fields)
{
foreach (var s in structs) {
foreach (var f in s.Fields) {
var match = Regex.Match(f.Type, @"Option<(.*)>");
// If the field type is an Option, extract the inner type and set Optional to true
if (match.Success)
{
if (match.Success) {
f.Type = match.Groups[1].Value;
f.Optional = true;
}
var match2 = Regex.Match(f.Type, @"Vec<(.*)>");
// If the field type is an Vec, extract the inner type and set Vec to true
if (match2.Success) {
f.Type = match2.Groups[1].Value;
f.Vec = true;
}
}
}
return structs;
}
}

View File

@@ -60,7 +60,15 @@
using (sb.Indent()) {
foreach (var field in rs.Fields) {
sb.AppendDocComment(field.DocComment);
sb.AppendLine($"pub {field.Name}: {GetBasicCTypeInRust(nameMap, field.Type)},");
var basicc = GetBasicCTypeInRust(nameMap, field.Type);
if (field.Vec) {
sb.AppendLine($"pub {field.Name}: *mut *mut {basicc},");
sb.AppendDocComment($"Count for {field.Name} Array");
sb.AppendLine($"pub {field.Name}Count: size_t,");
} else {
string opt = field.Optional && !basicc.StartsWith("*") ? "*mut " : "";
sb.AppendLine($"pub {field.Name}: {opt}{basicc},");
}
}
}
@@ -74,8 +82,10 @@
sb.AppendLine($"{rs.Name} {{");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"{field.Name}: c_to_{field.Type.ToLower()}{(field.Optional ? "_opt": "")}({(nameMap.ContainsKey(field.Type) ? "&" : "")}obj.{field.Name}),");
if (field.Vec) {
sb.AppendLine($"{field.Name}: c_to_{field.Type.ToLower()}_vec(obj.{field.Name}, obj.{field.Name}Count),");
} else if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"{field.Name}: c_to_{field.Type.ToLower()}{(field.Optional ? "_opt" : "")}({(nameMap.ContainsKey(field.Type) && !field.Optional ? "&" : "")}obj.{field.Name}),");
} else {
sb.AppendLine($"{field.Name}: obj.{field.Name},");
}
@@ -85,7 +95,7 @@
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub fn c_to_{rs.Name.ToLower()}_opt(obj: *mut {cName}) -> Option<{rs.Name}> {{");
using (sb.Indent()) {
@@ -94,7 +104,23 @@
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub fn c_to_{rs.Name.ToLower()}_vec(obj: *mut *mut {cName}, count: size_t) -> Vec<{rs.Name}> {{");
using (sb.Indent()) {
sb.AppendLine("if obj.is_null() || count == 0 { return Vec::new(); }");
sb.AppendLine("let mut assets = Vec::with_capacity(count as usize);");
sb.AppendLine("for i in 0..count {");
using (sb.Indent()) {
sb.AppendLine("let ptr = unsafe { *obj.add(i as usize) };");
sb.AppendLine($"assets.push(c_to_{rs.Name.ToLower()}(unsafe {{ &*ptr }}));");
}
sb.AppendLine("}");
sb.AppendLine("assets");
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub unsafe fn allocate_{rs.Name.ToLower()}(dto: {rs.Name}, obj: *mut {cName}) {{");
using (sb.Indent()) {
@@ -102,7 +128,18 @@
sb.AppendLine($"log::debug!(\"{cName} allocated\");");
foreach (var field in rs.Fields) {
if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"allocate_{field.Type.ToLower()}{(field.Optional ? "_opt": "")}(dto.{field.Name}, &mut (*obj).{field.Name});");
if (field.Optional && !(field.Type == "PathBuf" || field.Type == "String")) {
sb.AppendLine($"if let Some(opt) = dto.{field.Name} {{");
using (sb.Indent()) {
var fieldcName = nameMap[field.Type];
sb.AppendLine($"let ptr = libc::malloc(size_of::<{fieldcName}>()) as *mut {fieldcName};");
sb.AppendLine($"(*obj).{field.Name} = ptr;");
sb.AppendLine($"allocate_{field.Type.ToLower()}(opt, ptr);");
}
sb.AppendLine($"}}");
} else {
sb.AppendLine($"allocate_{field.Type.ToLower()}(dto.{field.Name}, &mut (*obj).{field.Name});");
}
} else {
sb.AppendLine($"(*obj).{field.Name} = dto.{field.Name};");
}
@@ -110,7 +147,7 @@
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub unsafe fn free_{rs.Name.ToLower()}(obj: *mut {cName}) {{");
using (sb.Indent()) {
@@ -118,7 +155,11 @@
sb.AppendLine($"log::debug!(\"{cName} freed\");");
foreach (var field in rs.Fields) {
if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"free_{field.Type.ToLower()}(&mut (*obj).{field.Name});");
if (field.Optional) {
sb.AppendLine($"free_{field.Type.ToLower()}((*obj).{field.Name});");
} else {
sb.AppendLine($"free_{field.Type.ToLower()}(&mut (*obj).{field.Name});");
}
}
}
}
@@ -126,7 +167,7 @@
sb.AppendLine();
}
private static string GetCPlusPlusType(string[] coreTypes, string rustType, bool optional)
private static string GetCPlusPlusType(string[] coreTypes, string rustType, bool optional, bool vec)
{
string type = rustType switch {
"PathBuf" => "std::string",
@@ -139,7 +180,9 @@
_ => coreTypes.Contains(rustType) ? rustType : throw new NotSupportedException("Unsupported type for c-plus-plus: " + rustType),
};
return optional ? "std::optional<" + type + ">" : type;
type = vec ? "std::vector<" + type + ">" : type;
type = optional ? "std::optional<" + type + ">" : type;
return type;
}
public static void WriteCBridgeMapping(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
@@ -288,7 +331,7 @@
foreach (var field in rs.Fields) {
using (sb.Indent()) {
sb.AppendDocComment(field.DocComment);
sb.AppendLine($"{GetCPlusPlusType(coreTypes, field.Type, field.Optional)} {field.Name};");
sb.AppendLine($"{GetCPlusPlusType(coreTypes, field.Type, field.Optional, field.Vec)} {field.Name};");
}
}

View File

@@ -0,0 +1,98 @@
{{#each this}}
#[rustfmt::skip]
#[repr(C)]
{{rust_comment}}
pub struct {{struct_c_name}} {
{{#each fields}}
{{rust_comment}}
pub {{field_name}}: {{#unless field_primitive}}*mut {{/unless}}{{~#if field_vector}}*mut {{/if}}{{field_c_type}},
{{#if field_vector}}
/// The number of elements in the {{field_name}} array.
pub {{field_name}}Count: size_t,
{{/if}}
{{/each}}
}
#[rustfmt::skip]
pub fn c_to_{{struct_rust_name}}(obj: *mut {{struct_c_name}}) -> Result<{{struct_rust_name}}> {
if obj.is_null() { bail!("Null pointer: {{struct_rust_name}} must be set."); }
let obj = unsafe { &*obj };
let result = {{struct_rust_name}} {
{{#each fields}}
{{#if field_normal}}{{field_name}}: c_to_{{field_rust_type}}(obj.{{field_name}}){{#if field_optional}}.ok(){{else}}?{{/if}},{{/if~}}
{{#if field_vector}}{{field_name}}: c_to_{{field_rust_type}}_vec(obj.{{field_name}}, obj.{{field_name}}Count)?,{{/if~}}
{{#if field_primitive}}{{field_name}}: obj.{{field_name}},{{/if}}
{{/each}}
};
Ok(result)
}
#[rustfmt::skip]
pub fn c_to_{{struct_rust_name}}_vec(obj: *mut *mut {{struct_c_name}}, count: size_t) -> Result<Vec<{{struct_rust_name}}>> {
if obj.is_null() || count == 0 { return Ok(Vec::new()); }
let mut assets = Vec::with_capacity(count as usize);
for i in 0..count {
let ptr = unsafe { *obj.add(i as usize) };
assets.push(c_to_{{struct_rust_name}}(ptr)?);
}
Ok(assets)
}
#[rustfmt::skip]
pub unsafe fn allocate_{{struct_rust_name}}<'a, T: Into<Option<&'a {{struct_rust_name}}>>>(dto: T) -> *mut {{struct_c_name}} {
let dto = dto.into();
if dto.is_none() {
return std::ptr::null_mut();
}
log::debug!("{{struct_c_name}} allocated");
let dto = dto.unwrap();
let obj = libc::malloc(size_of::<{{struct_c_name}}>()) as *mut {{struct_c_name}};
{{#each fields}}
{{#if field_normal}}(*obj).{{field_name}} = allocate_{{field_rust_type}}(&dto.{{field_name}});{{/if~}}
{{#if field_vector}}(*obj).{{field_name}} = allocate_{{field_rust_type}}_vec(&dto.{{field_name}}, &mut (*obj).{{field_name}}Count);{{/if~}}
{{#if field_primitive}}(*obj).{{field_name}} = dto.{{field_name}};{{/if}}
{{/each}}
obj
}
#[rustfmt::skip]
pub unsafe fn allocate_{{struct_rust_name}}_vec(dto: &Vec<{{struct_rust_name}}>, count: *mut size_t) -> *mut *mut {{struct_c_name}} {
if dto.is_empty() {
*count = 0;
return std::ptr::null_mut();
}
log::debug!("{{struct_c_name}} vector allocated");
let count_value = dto.len() as size_t;
let mut assets = Vec::with_capacity(count_value as usize);
for i in 0..count_value {
let ptr = allocate_{{struct_rust_name}}(&dto[i as usize]);
assets.push(ptr);
}
let ptr = assets.as_mut_ptr();
std::mem::forget(assets);
ptr
}
#[rustfmt::skip]
pub unsafe fn free_{{struct_rust_name}}(obj: *mut {{struct_c_name}}) {
if obj.is_null() { return; }
{{#each fields}}
{{#if field_vector}}free_{{field_rust_type}}_vec((*obj).{{field_name}}, (*obj).{{field_name}}Count);{{/if~}}
{{#if field_normal}}free_{{field_rust_type}}((*obj).{{field_name}});{{/if}}
{{/each}}
libc::free(obj as *mut c_void);
log::debug!("{{struct_c_name}} freed");
}
#[rustfmt::skip]
pub unsafe fn free_{{struct_rust_name}}_vec(obj: *mut *mut {{struct_c_name}}, count: size_t) {
if obj.is_null() || count == 0 { return; }
let vec = Vec::from_raw_parts(obj, count as usize, count as usize);
for i in 0..count {
let ptr = *vec.get_unchecked(i as usize);
free_{{struct_rust_name}}(ptr);
}
log::debug!("{{struct_c_name}} vector freed");
}
{{/each}}

View File

@@ -6,7 +6,7 @@
ReplaceTextBetween(ref body, placeholderName, text);
File.WriteAllText(path, body);
}
public static void ReplaceTextBetween(ref string body, string placeholderName, string text)
{
var start = $"// !! AUTO-GENERATED-START {placeholderName}";
@@ -24,4 +24,11 @@
body = body.Remove(startIndex, endIndex - startIndex);
body = body.Insert(startIndex, text.TrimEnd());
}
public static string PrefixEveryLine(this string text, string prefix)
{
if (string.IsNullOrEmpty(prefix)) { return text; }
var lines = text.ReplaceLineEndings("\n").Split(['\n']).Select(l => prefix + l);
return String.Join("\n", lines);
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Superpower" Version="3.0.0" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
</ItemGroup>
<ItemGroup>
@@ -18,4 +19,8 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<Folder Include="Templates\" />
</ItemGroup>
</Project>

View File

@@ -9,6 +9,14 @@ export type UpdateInfo = {
* The available version that we are updating to.
*/
TargetFullRelease: VelopackAsset,
/**
* The base release that this update is based on. This is only available if the update is a delta update.
*/
BaseRelease: VelopackAsset | null,
/**
* The list of delta updates that can be applied to the base version to get to the target version.
*/
DeltasToTarget: Array<VelopackAsset>,
/**
* 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

View File

@@ -12,7 +12,7 @@ export type UpdateOptions = {
*/
AllowVersionDowngrade: boolean,
/**
* **This option should usually be left None**. <br/>
* **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'.
@@ -20,4 +20,9 @@ AllowVersionDowngrade: boolean,
* 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.
*/
ExplicitChannel: string | null, };
ExplicitChannel: string | null,
/**
* 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.
*/
MaximumDeltasBeforeFallback: number, };

View File

@@ -126,7 +126,7 @@ pub struct UpdateOptions {
/// ExplicitChannel to switch channels to another channel where the latest version on that
/// channel is lower than the current version.
pub AllowVersionDowngrade: bool,
/// **This option should usually be left None**. <br/>
/// **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'.