diff --git a/src/lib-python/build.rs b/src/lib-python/build.rs index e5c5b794..3de0c482 100644 --- a/src/lib-python/build.rs +++ b/src/lib-python/build.rs @@ -3,40 +3,20 @@ use std::fs; use std::path::Path; fn main() { - - - - - - - - - - - - - - - - - - // Get the workspace version - let version = get_workspace_version().unwrap_or_else(|| { - env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string()) - }); - + let version = get_workspace_version().unwrap_or_else(|| env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string())); + let python_version = convert_to_python_version(&version); - + // Set environment variables for PyO3 println!("cargo:rustc-env=PYTHON_VERSION={}", python_version); - + // Try setting the package version for PyO3 to pick up println!("cargo:metadata=version={}", python_version); - + // Also set it as a cfg value println!("cargo:rustc-cfg=version=\"{}\"", python_version); - + println!("cargo:rerun-if-changed=../../Cargo.toml"); } @@ -44,33 +24,32 @@ fn get_workspace_version() -> Option { // Navigate up to workspace root and read Cargo.toml let manifest_dir = env::var("CARGO_MANIFEST_DIR").ok()?; let workspace_toml = Path::new(&manifest_dir) - .parent()? // src - .parent()? // velopack root + .parent()? // src + .parent()? // velopack root .join("Cargo.toml"); - + if !workspace_toml.exists() { return None; } - + let content = fs::read_to_string(&workspace_toml).ok()?; - + // Simple parsing to extract version from [workspace.package] section let mut in_workspace_package = false; for (_line_num, line) in content.lines().enumerate() { let trimmed = line.trim(); - + if trimmed == "[workspace.package]" { in_workspace_package = true; continue; } - + if trimmed.starts_with('[') && trimmed != "[workspace.package]" { - if in_workspace_package { - } + if in_workspace_package {} in_workspace_package = false; continue; } - + if in_workspace_package && trimmed.starts_with("version") { if let Some(equals_pos) = trimmed.find('=') { let version_part = &trimmed[equals_pos + 1..].trim(); @@ -80,7 +59,7 @@ fn get_workspace_version() -> Option { } } } - + None } @@ -90,38 +69,38 @@ fn convert_to_python_version(rust_version: &str) -> String { let base = &rust_version[..git_pos]; return ensure_xyz_format(base); } - + // Handle local development versions like "0.0.0-local" - drop local suffix if rust_version.ends_with("-local") { let base = rust_version.trim_end_matches("-local"); return ensure_xyz_format(base); } - + // Handle Rust pre-release patterns and convert to Python equivalents if rust_version.contains("-alpha") { let base = rust_version.split("-alpha").next().unwrap(); let alpha_num = extract_prerelease_number(rust_version, "-alpha"); return format!("{}a{}", ensure_xyz_format(base), alpha_num); } - + if rust_version.contains("-beta") { let base = rust_version.split("-beta").next().unwrap(); let beta_num = extract_prerelease_number(rust_version, "-beta"); return format!("{}b{}", ensure_xyz_format(base), beta_num); } - + if rust_version.contains("-rc") { let base = rust_version.split("-rc").next().unwrap(); let rc_num = extract_prerelease_number(rust_version, "-rc"); return format!("{}rc{}", ensure_xyz_format(base), rc_num); } - + // For any other dash-separated version, just take the base if rust_version.contains('-') { let base = rust_version.split('-').next().unwrap(); return ensure_xyz_format(base); } - + ensure_xyz_format(rust_version) } @@ -159,23 +138,23 @@ mod tests { // Git versions - drop git ref assert_eq!(convert_to_python_version("0.0.1213-g57cf68d"), "0.0.1213"); assert_eq!(convert_to_python_version("1.2-g57cf68d"), "1.2.0"); - + // Local versions - drop local suffix assert_eq!(convert_to_python_version("0.0.0-local"), "0.0.0"); assert_eq!(convert_to_python_version("1.2.3-local"), "1.2.3"); - + // Pre-release versions - convert to Python format assert_eq!(convert_to_python_version("1.0.0-alpha.1"), "1.0.0a1"); assert_eq!(convert_to_python_version("1.0.0-alpha"), "1.0.0a0"); assert_eq!(convert_to_python_version("1.0.0-beta.2"), "1.0.0b2"); assert_eq!(convert_to_python_version("1.0.0-rc.1"), "1.0.0rc1"); - + // Standard versions - ensure x.y.z format assert_eq!(convert_to_python_version("1.0.0"), "1.0.0"); assert_eq!(convert_to_python_version("1.2"), "1.2.0"); assert_eq!(convert_to_python_version("1"), "1.0.0"); - + // Other dash-separated versions - take base only assert_eq!(convert_to_python_version("1.0.0-something-else"), "1.0.0"); } -} \ No newline at end of file +} diff --git a/src/lib-python/src/app.rs b/src/lib-python/src/app.rs index 73da1acb..127dab76 100644 --- a/src/lib-python/src/app.rs +++ b/src/lib-python/src/app.rs @@ -85,8 +85,7 @@ impl VelopackAppWrapper { /// Runs the Velopack startup logic pub fn run(&mut self, _py: Python) -> PyResult<()> { // Create the Rust VelopackApp with our stored configuration - let mut app = VelopackAppRust::build() - .set_auto_apply_on_startup(self.auto_apply); + let mut app = VelopackAppRust::build().set_auto_apply_on_startup(self.auto_apply); // Set args if provided if let Some(ref args) = self.args { @@ -175,4 +174,3 @@ impl VelopackAppWrapper { Ok(()) } } - diff --git a/src/lib-python/src/exceptions.rs b/src/lib-python/src/exceptions.rs index 6457fcbe..6779923e 100644 --- a/src/lib-python/src/exceptions.rs +++ b/src/lib-python/src/exceptions.rs @@ -1,5 +1,5 @@ -use pyo3::prelude::*; use pyo3::exceptions::PyException; +use pyo3::prelude::*; #[pyclass(name="VelopackError", extends=PyException, module="velopack.exceptions")] #[derive(Debug)] @@ -18,4 +18,3 @@ impl VelopackError { self.message.clone() } } - diff --git a/src/lib-python/src/lib.rs b/src/lib-python/src/lib.rs index 8fcf5b56..d3131830 100644 --- a/src/lib-python/src/lib.rs +++ b/src/lib-python/src/lib.rs @@ -1,40 +1,37 @@ -use asset::PyUpdateInfo; use pyo3::prelude::*; use pyo3::types::PyModule; -mod asset; -pub use asset::PyVelopackAsset; - +mod types; +use types::*; mod exceptions; -pub use exceptions::VelopackError; +use exceptions::VelopackError; mod app; -pub use app::VelopackAppWrapper; +use app::VelopackAppWrapper; mod manager; -pub use manager::UpdateManagerWrapper; - +use manager::UpdateManagerWrapper; #[pymodule] fn velopack(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; + // auto-generated DTO's m.add_class::()?; m.add_class::()?; - - + m.add_class::()?; + m.add_class::()?; + // concrete classes m.add_class::()?; m.add_class::()?; // add __version__ attribute m.add("__version__", env!("CARGO_PKG_VERSION"))?; - - - + // add __author__ attribute m.add("__author__", env!("CARGO_PKG_AUTHORS"))?; - + Ok(()) -} \ No newline at end of file +} diff --git a/src/lib-python/src/manager.rs b/src/lib-python/src/manager.rs index 589e98d8..07ec1e0a 100644 --- a/src/lib-python/src/manager.rs +++ b/src/lib-python/src/manager.rs @@ -1,13 +1,12 @@ +use pyo3::prelude::*; use std::sync::mpsc; use std::thread; -use pyo3::prelude::*; - -use velopack::{UpdateCheck, UpdateInfo, UpdateManager as VelopackUpdateManagerRust}; use velopack::sources::AutoSource; +use velopack::{UpdateCheck, UpdateInfo, UpdateManager as VelopackUpdateManagerRust}; use crate::exceptions::VelopackError; -use crate::asset::PyUpdateInfo; +use crate::types::*; #[pyclass(name = "UpdateManager")] pub struct UpdateManagerWrapper { @@ -17,88 +16,80 @@ pub struct UpdateManagerWrapper { #[pymethods] impl UpdateManagerWrapper { // for new, just take in a string, which is the source -#[new] -pub fn new(source: String) -> PyResult { - let source = AutoSource::new(&source); - // set myinner to a new VelopackUpdateManager with the source - let inner = VelopackUpdateManagerRust::new(source, None, None) - .map_err(|e| PyErr::new::(format!("Failed to create UpdateManager: {}", e)))?; - Ok(UpdateManagerWrapper { - inner, -} - ) -} + #[new] + #[pyo3(signature = (source, options = None, locator = None))] + pub fn new(source: String, options: Option, locator: Option) -> PyResult { + let source = AutoSource::new(&source); + // set myinner to a new VelopackUpdateManager with the source + let inner = VelopackUpdateManagerRust::new(source, options.map(Into::into), locator.map(Into::into)) + .map_err(|e| PyErr::new::(format!("Failed to create UpdateManager: {}", e)))?; + Ok(UpdateManagerWrapper { inner }) + } - -// check_for_updates return update info indicating if updates are available + // check_for_updates return update info indicating if updates are available /// This method checks for updates and returns update info if updates are available, None otherwise. -pub fn check_for_updates(&mut self) -> PyResult> { - let update_check = self.inner.check_for_updates() - .map_err(|e| PyErr::new::(format!("Failed to check for updates: {}", e)))?; - - match update_check { - UpdateCheck::UpdateAvailable(updates) => { - let py_updates = PyUpdateInfo::from(updates); - Ok(Some(py_updates)) - }, - UpdateCheck::NoUpdateAvailable => { - Ok(None) - }, - UpdateCheck::RemoteIsEmpty => { - Ok(None) + pub fn check_for_updates(&mut self) -> PyResult> { + let update_check = + self.inner.check_for_updates().map_err(|e| PyErr::new::(format!("Failed to check for updates: {}", e)))?; + + match update_check { + UpdateCheck::UpdateAvailable(updates) => { + let py_updates = PyUpdateInfo::from(updates); + Ok(Some(py_updates)) + } + UpdateCheck::NoUpdateAvailable => Ok(None), + UpdateCheck::RemoteIsEmpty => Ok(None), } } -} + #[pyo3(signature = (update_info, progress_callback = None))] + pub fn download_updates(&mut self, update_info: &PyUpdateInfo, progress_callback: Option) -> PyResult<()> { + // Convert PyUpdateInfo back to rust UpdateInfo + let rust_update_info: UpdateInfo = update_info.clone().into(); -#[pyo3(signature = (update_info, progress_callback = None))] -pub fn download_updates(&mut self, update_info: &PyUpdateInfo, progress_callback: Option) -> PyResult<()> { - // Convert PyUpdateInfo back to rust UpdateInfo - let rust_update_info: UpdateInfo = update_info.clone().into(); - - if let Some(callback) = progress_callback { - // Create a channel for progress updates - let (sender, receiver) = mpsc::channel::(); - - // Spawn a thread to handle progress updates - let progress_thread = thread::spawn(move || { - Python::with_gil(|py| { - while let Ok(progress) = receiver.recv() { - if let Err(e) = callback.call1(py, (progress,)) { - // Log error but continue - don't break the download - eprintln!("Progress callback error: {}", e); - break; + if let Some(callback) = progress_callback { + // Create a channel for progress updates + let (sender, receiver) = mpsc::channel::(); + + // Spawn a thread to handle progress updates + let progress_thread = thread::spawn(move || { + Python::with_gil(|py| { + while let Ok(progress) = receiver.recv() { + if let Err(e) = callback.call1(py, (progress,)) { + // Log error but continue - don't break the download + eprintln!("Progress callback error: {}", e); + break; + } } - } + }); }); - }); - - // Call download with the sender - let result = self.inner.download_updates(&rust_update_info, Some(sender)) - .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))); - - // Wait for the progress thread to finish - let _ = progress_thread.join(); - - result.map(|_| ()) - } else { - // No progress callback provided - self.inner.download_updates(&rust_update_info, None) - .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))) + + // Call download with the sender + let result = self + .inner + .download_updates(&rust_update_info, Some(sender)) + .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))); + + // Wait for the progress thread to finish + let _ = progress_thread.join(); + + result.map(|_| ()) + } else { + // No progress callback provided + self.inner + .download_updates(&rust_update_info, None) + .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))) + .map(|_| ()) + } + } + + pub fn apply_updates_and_restart(&mut self, update_info: &PyUpdateInfo) -> PyResult<()> { + // Convert PyUpdateInfo back to rust UpdateInfo + let rust_update_info: UpdateInfo = update_info.clone().into(); + + self.inner + .apply_updates_and_restart(&rust_update_info) + .map_err(|e| PyErr::new::(format!("Failed to apply updates and restart: {}", e))) .map(|_| ()) } } - -pub fn apply_updates_and_restart(&mut self, update_info: &PyUpdateInfo) -> PyResult<()> { - // Convert PyUpdateInfo back to rust UpdateInfo - let rust_update_info: UpdateInfo = update_info.clone().into(); - - self.inner.apply_updates_and_restart(&rust_update_info) - .map_err(|e| PyErr::new::(format!("Failed to apply updates and restart: {}", e))) - .map(|_| ()) -} - - - -} -