mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
wip JS bindings
This commit is contained in:
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -2049,6 +2049,27 @@ version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "9.0.1"
|
||||
source = "git+https://github.com/Aleph-Alpha/ts-rs.git?branch=cli#7c9643e16038dd82e8ae2faf7914cdda6941b0a2"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"thiserror",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "9.0.1"
|
||||
source = "git+https://github.com/Aleph-Alpha/ts-rs.git?branch=cli#7c9643e16038dd82e8ae2faf7914cdda6941b0a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
@@ -2126,6 +2147,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"ureq",
|
||||
"url",
|
||||
"xml",
|
||||
@@ -2193,6 +2215,8 @@ name = "velopack_nodeffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"neon",
|
||||
"serde_json",
|
||||
"ts-rs",
|
||||
"velopack",
|
||||
]
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use std::{fs, path::Path, path::PathBuf};
|
||||
use tempfile::tempdir;
|
||||
use velopack_bins::*;
|
||||
|
||||
use velopack_bins::logging::trace_logger;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winsafe::{self as w, co};
|
||||
|
||||
@@ -14,7 +13,6 @@ use winsafe::{self as w, co};
|
||||
#[test]
|
||||
pub fn test_install_apply_uninstall() {
|
||||
dialogs::set_silent(true);
|
||||
trace_logger();
|
||||
|
||||
let fixtures = find_fixtures();
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
|
||||
|
||||
installing ts tool: cargo install --git https://github.com/Aleph-Alpha/ts-rs.git --branch cli cargo-ts
|
||||
|
||||
|
||||
|
||||
|
||||
# veloz
|
||||
|
||||
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
|
||||
|
||||
@@ -12,4 +12,6 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
neon = "1"
|
||||
serde_json = "1"
|
||||
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs.git", branch = "cli" }
|
||||
velopack = { path = "../../../lib-rust" }
|
||||
|
||||
@@ -1,49 +1,97 @@
|
||||
use neon::prelude::*;
|
||||
use velopack::*;
|
||||
use std::cell::RefCell;
|
||||
use std::thread;
|
||||
use velopack::sources::*;
|
||||
use velopack::*;
|
||||
|
||||
#[derive(ts_rs::TS)]
|
||||
#[ts(as = "T")]
|
||||
pub struct Wrapper<T: ts_rs::TS>(T);
|
||||
|
||||
struct UpdateManagerWrapper<'a> {
|
||||
manager: UpdateManager<'a>,
|
||||
#[derive(ts_rs::TS)]
|
||||
#[ts(export, export_to = "../../../bindings/")]
|
||||
#[allow(dead_code)]
|
||||
struct TsBindings {
|
||||
pub t1: UpdateInfo,
|
||||
pub t2: UpdateOptions,
|
||||
}
|
||||
|
||||
impl<'a> Finalize for UpdateManagerWrapper<'a> {}
|
||||
struct UpdateManagerWrapper {
|
||||
manager: UpdateManager,
|
||||
}
|
||||
impl Finalize for UpdateManagerWrapper {}
|
||||
type BoxedUpdateManager = JsBox<RefCell<UpdateManagerWrapper>>;
|
||||
|
||||
fn js_new_update_manager(mut cx: FunctionContext) -> JsResult<BoxedUpdateManager> {
|
||||
let arg_source = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||
let arg_options = cx.argument::<JsString>(1)?.value(&mut cx);
|
||||
|
||||
fn get_js_options(mut cx: FunctionContext, obj: &Handle<JsObject>) -> JsResult<UpdateOptions> {
|
||||
let allow_downgrade = obj.get(&mut cx, "allowDowngrade")?;
|
||||
let mut options: Option<UpdateOptions> = None;
|
||||
|
||||
if !arg_options.is_empty() {
|
||||
let new_opt = serde_json::from_str::<UpdateOptions>(&arg_options).or_else(|e| cx.throw_error(e.to_string()))?;
|
||||
options = Some(new_opt);
|
||||
}
|
||||
|
||||
let source = AutoSource::new(&arg_source);
|
||||
let manager = UpdateManager::new(source, options).or_else(|e| cx.throw_error(e.to_string()))?;
|
||||
let wrapper = UpdateManagerWrapper { manager };
|
||||
Ok(cx.boxed(RefCell::new(wrapper)))
|
||||
}
|
||||
|
||||
fn js_new_from_http_source(mut cx: FunctionContext) -> JsResult<JsBox<UpdateManagerWrapper>> {
|
||||
let url = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||
|
||||
let options: Option<UpdateOptions> = None;
|
||||
|
||||
|
||||
let obj = cx.argument::<JsObject>(1)?;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let source = HttpSource::new(&url);
|
||||
let um = UpdateManager::new(source, options).map_err(|e| cx.throw_error(e.to_string()))?;
|
||||
|
||||
let wrapper = UpdateManagerWrapper { manager: um };
|
||||
|
||||
|
||||
Ok(cx.boxed(wrapper))
|
||||
fn js_get_current_version(mut cx: FunctionContext) -> JsResult<JsString> {
|
||||
let this = cx.this::<BoxedUpdateManager>()?;
|
||||
let version = this.borrow().manager.current_version().or_else(|e| cx.throw_error(e.to_string()))?;
|
||||
Ok(cx.string(version))
|
||||
}
|
||||
|
||||
fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
|
||||
Ok(cx.string("hello node"))
|
||||
fn js_check_for_updates_async(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||
let this = cx.this::<BoxedUpdateManager>()?;
|
||||
let (deferred, promise) = cx.promise();
|
||||
let channel = cx.channel();
|
||||
let manager = this.borrow().manager.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let result = manager.check_for_updates();
|
||||
channel.send(move |mut cx| {
|
||||
match result {
|
||||
Ok(res) => {
|
||||
if let UpdateCheck::UpdateAvailable(upd) = &res {
|
||||
let json = serde_json::to_string(&upd);
|
||||
if let Err(e) = &json {
|
||||
let err = cx.error(e.to_string()).unwrap();
|
||||
deferred.reject(&mut cx, err);
|
||||
} else {
|
||||
let val = cx.string(json.unwrap());
|
||||
deferred.resolve(&mut cx, val);
|
||||
}
|
||||
} else {
|
||||
let nil = cx.null();
|
||||
deferred.resolve(&mut cx, nil);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let err = cx.error(e.to_string()).unwrap();
|
||||
deferred.reject(&mut cx, err);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
});
|
||||
});
|
||||
|
||||
Ok(promise)
|
||||
}
|
||||
|
||||
#[neon::main]
|
||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
cx.export_function("hello", hello)?;
|
||||
cx.export_function("js_new_update_manager", js_new_update_manager)?;
|
||||
cx.export_function("js_get_current_version", js_get_current_version)?;
|
||||
// cx.export_function("js_get_app_id", js_get_app_id)?;
|
||||
// cx.export_function("js_is_portable", js_is_portable)?;
|
||||
// cx.export_function("js_is_installed", js_is_installed)?;
|
||||
// cx.export_function("js_is_update_pending_restart", js_is_update_pending_restart)?;
|
||||
cx.export_function("js_check_for_updates_async", js_check_for_updates_async)?;
|
||||
// cx.export_function("js_download_update_async", js_download_update_async)?;
|
||||
// cx.export_function("js_wait_then_apply_update_async", js_wait_then_apply_update_async)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,19 +1,86 @@
|
||||
// This module is the CJS entry point for the library.
|
||||
|
||||
// The Rust addon.
|
||||
import * as addon from './load.cjs';
|
||||
|
||||
// Use this declaration to assign types to the addon's exports,
|
||||
// which otherwise by default are `any`.
|
||||
type UpdateManagerOpaque = {};
|
||||
declare module "./load.cjs" {
|
||||
function hello(): string;
|
||||
function js_new_update_manager(urlOrPath: string, options?: string): UpdateManagerOpaque;
|
||||
function js_get_current_version(um: UpdateManagerOpaque): string;
|
||||
// function js_get_app_id(um: UpdateManagerOpaque): string;
|
||||
// function js_is_portable(um: UpdateManagerOpaque): boolean;
|
||||
// function js_is_installed(um: UpdateManagerOpaque): boolean;
|
||||
// function js_is_update_pending_restart(um: UpdateManagerOpaque): boolean;
|
||||
function js_check_for_updates_async(um: UpdateManagerOpaque): Promise<string | null>;
|
||||
function js_download_update_async(um: UpdateManagerOpaque, update: string, progress: (perc: number) => void, ignoreDeltas: boolean): Promise<void>;
|
||||
function js_wait_then_apply_update_async(um: UpdateManagerOpaque, update?: string): Promise<void>;
|
||||
}
|
||||
|
||||
export type Greeting = {
|
||||
message: string
|
||||
};
|
||||
|
||||
export function greeting(): Greeting {
|
||||
const message = addon.hello();
|
||||
return { message };
|
||||
export type UpdateOptions = {
|
||||
AllowVersionDowngrade: boolean;
|
||||
ExplicitChannel: string;
|
||||
}
|
||||
|
||||
/** An individual Velopack asset, could refer to an asset on-disk or in a remote package feed. */
|
||||
export type VelopackAsset = {
|
||||
FileName: string;
|
||||
Version: string;
|
||||
NotesHtml: string;
|
||||
NotesMarkdown: string;
|
||||
PackageId: string;
|
||||
SHA1: string;
|
||||
SHA256: string;
|
||||
Size: number;
|
||||
Type: "Full" | "Delta";
|
||||
}
|
||||
|
||||
export type UpdateInfo = {
|
||||
BaseRelease: VelopackAsset;
|
||||
DeltasToTarget: VelopackAsset[];
|
||||
IsDowngrade: boolean;
|
||||
TargetFullRelease: VelopackAsset;
|
||||
}
|
||||
|
||||
export class UpdateManager {
|
||||
|
||||
private opaque: UpdateManagerOpaque;
|
||||
|
||||
constructor(urlOrPath: string, options?: UpdateOptions) {
|
||||
this.opaque = addon.js_new_update_manager(urlOrPath, options ? JSON.stringify(options) : "");
|
||||
}
|
||||
|
||||
getCurrentVersion(): string {
|
||||
return addon.js_get_current_version.call(this.opaque);
|
||||
}
|
||||
|
||||
// getAppId(): string {
|
||||
// return addon.js_get_app_id.call(this.opaque);
|
||||
// }
|
||||
|
||||
// isInstalled(): boolean {
|
||||
// return addon.js_is_installed.call(this.opaque);
|
||||
// }
|
||||
|
||||
// isPortable(): boolean {
|
||||
// return addon.js_is_portable.call(this.opaque);
|
||||
// }
|
||||
|
||||
// isUpdatePendingRestart(): boolean {
|
||||
// return addon.js_is_update_pending_restart.call(this.opaque);
|
||||
// }
|
||||
|
||||
checkForUpdatesAsync(): Promise<UpdateInfo | null> {
|
||||
let json: Promise<string> = addon.js_check_for_updates_async.call(this.opaque);
|
||||
return json.then((json) => {
|
||||
if (json && json.length > 0) {
|
||||
return JSON.parse(json);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
downloadUpdateAsync(update: UpdateInfo, progress: (perc: number) => void, ignoreDeltas = false): Promise<void> {
|
||||
return addon.js_download_update_async.call(this.opaque, JSON.stringify(update), progress, ignoreDeltas);
|
||||
}
|
||||
|
||||
waitExitThenApplyUpdateAsync(update: UpdateInfo): Promise<void> {
|
||||
return addon.js_wait_then_apply_update_async.call(this.opaque, JSON.stringify(update));
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
zip = { version = "2.1", default-features = false, features = ["deflate"] }
|
||||
thiserror = "1.0"
|
||||
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs.git", branch = "cli" }
|
||||
|
||||
# delta packages
|
||||
zstd = { version = "0.13", optional = true }
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::{fs, process::{exit, Command as Process}, rc::Rc, sync::mpsc::Sender};
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::{
|
||||
fs,
|
||||
process::{exit, Command as Process},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use async_std::channel::Sender;
|
||||
@@ -9,7 +13,11 @@ use async_std::task::JoinHandle;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, locator::{self, VelopackLocator}, sources::UpdateSource};
|
||||
use crate::{
|
||||
locator::{self, VelopackLocator},
|
||||
sources::UpdateSource,
|
||||
Error,
|
||||
};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
@@ -28,7 +36,7 @@ impl VelopackAssetFeed {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, ts_rs::TS)]
|
||||
#[serde(default)]
|
||||
/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
|
||||
pub struct VelopackAsset {
|
||||
@@ -53,7 +61,7 @@ pub struct VelopackAsset {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, ts_rs::TS)]
|
||||
#[serde(default)]
|
||||
/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
|
||||
pub struct UpdateInfo {
|
||||
@@ -71,8 +79,9 @@ impl AsRef<VelopackAsset> for UpdateInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, ts_rs::TS)]
|
||||
#[serde(default)]
|
||||
/// Options to customise the behaviour of UpdateManager.
|
||||
pub struct UpdateOptions {
|
||||
/// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
|
||||
@@ -91,11 +100,11 @@ pub struct UpdateOptions {
|
||||
}
|
||||
|
||||
/// Provides functionality for checking for updates, downloading updates, and applying updates to the current application.
|
||||
pub struct UpdateManager<'a>
|
||||
{
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateManager {
|
||||
allow_version_downgrade: bool,
|
||||
explicit_channel: Option<String>,
|
||||
source: Rc<Box<dyn UpdateSource + 'a>>,
|
||||
source: Box<dyn UpdateSource>,
|
||||
paths: VelopackLocator,
|
||||
}
|
||||
|
||||
@@ -143,7 +152,7 @@ pub enum UpdateCheck {
|
||||
UpdateAvailable(UpdateInfo),
|
||||
}
|
||||
|
||||
impl<'a> UpdateManager<'a> {
|
||||
impl UpdateManager {
|
||||
/// Create a new UpdateManager instance using the specified UpdateSource.
|
||||
/// This will return an error if the application is not yet installed.
|
||||
/// ## Example:
|
||||
@@ -153,12 +162,12 @@ impl<'a> UpdateManager<'a> {
|
||||
/// let source = sources::HttpSource::new("https://the.place/you-host/updates");
|
||||
/// let um = UpdateManager::new(source, None);
|
||||
/// ```
|
||||
pub fn new<T: UpdateSource + 'a>(source: T, options: Option<UpdateOptions>) -> Result<UpdateManager::<'a>, Error> {
|
||||
pub fn new<T: UpdateSource>(source: T, options: Option<UpdateOptions>) -> Result<UpdateManager, Error> {
|
||||
Ok(UpdateManager {
|
||||
paths: locator::auto_locate()?,
|
||||
allow_version_downgrade: options.as_ref().map(|f| f.AllowVersionDowngrade).unwrap_or(false),
|
||||
explicit_channel: options.as_ref().map(|f| f.ExplicitChannel.clone()).unwrap_or(None),
|
||||
source: Rc::new(Box::new(source)),
|
||||
source: source.clone_boxed(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,7 +260,8 @@ impl<'a> UpdateManager<'a> {
|
||||
/// 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.
|
||||
pub fn check_for_updates_async(&self) -> JoinHandle<Result<UpdateCheck, Error>>
|
||||
where T: 'static,
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let self_clone = self.clone();
|
||||
async_std::task::spawn_blocking(move || self_clone.check_for_updates())
|
||||
@@ -263,8 +273,7 @@ impl<'a> UpdateManager<'a> {
|
||||
/// 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.
|
||||
pub fn download_updates(&self, update: &UpdateInfo, progress: Option<Sender<i16>>) -> Result<(), Error>
|
||||
{
|
||||
pub fn download_updates(&self, update: &UpdateInfo, progress: Option<Sender<i16>>) -> Result<(), Error> {
|
||||
let name = &update.TargetFullRelease.FileName;
|
||||
let packages_dir = &self.paths.packages_dir;
|
||||
fs::create_dir_all(packages_dir)?;
|
||||
@@ -324,8 +333,7 @@ impl<'a> UpdateManager<'a> {
|
||||
/// 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.
|
||||
pub fn download_updates_async(&self, update: &UpdateInfo, progress: Option<Sender<i16>>) -> JoinHandle<Result<(), Error>>
|
||||
{
|
||||
pub fn download_updates_async(&self, update: &UpdateInfo, progress: Option<Sender<i16>>) -> JoinHandle<Result<(), Error>> {
|
||||
let self_clone = self.clone();
|
||||
let update_clone = update.clone();
|
||||
async_std::task::spawn_blocking(move || self_clone.download_updates(&update_clone, progress))
|
||||
|
||||
@@ -14,6 +14,54 @@ pub trait UpdateSource: Send + Sync {
|
||||
fn get_release_feed(&self, channel: &str, app: &manifest::Manifest) -> Result<VelopackAssetFeed, Error>;
|
||||
/// Download the specified VelopackAsset to the provided local file path.
|
||||
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error>;
|
||||
/// Clone the source to create a new lifetime.
|
||||
fn clone_boxed(&self) -> Box<dyn UpdateSource>;
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn UpdateSource> {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Automatically delegates to the appropriate source based on the provided input string. If the input is a local path,
|
||||
/// it will use a FileSource. If the input is a URL, it will use an HttpSource.
|
||||
pub struct AutoSource {
|
||||
source: Box<dyn UpdateSource>,
|
||||
}
|
||||
|
||||
impl AutoSource {
|
||||
/// Create a new AutoSource with the specified input string.
|
||||
pub fn new(input: &str) -> AutoSource {
|
||||
let source: Box<dyn UpdateSource> = if Self::is_http_url(input) {
|
||||
Box::new(HttpSource::new(input))
|
||||
} else {
|
||||
Box::new(FileSource::new(input))
|
||||
};
|
||||
AutoSource { source }
|
||||
}
|
||||
|
||||
fn is_http_url(url: &str) -> bool {
|
||||
match url::Url::parse(url) {
|
||||
Ok(url) => url.scheme().eq_ignore_ascii_case("http") || url.scheme().eq_ignore_ascii_case("https"),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateSource for AutoSource {
|
||||
fn get_release_feed(&self, channel: &str, app: &manifest::Manifest) -> Result<VelopackAssetFeed, Error> {
|
||||
self.source.get_release_feed(channel, app)
|
||||
}
|
||||
|
||||
fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
|
||||
self.source.download_release_entry(asset, local_file, progress_sender)
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn UpdateSource> {
|
||||
self.source.clone_boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -59,6 +107,10 @@ impl UpdateSource for HttpSource {
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn UpdateSource> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -99,4 +151,8 @@ impl UpdateSource for FileSource {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn UpdateSource> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user