mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Pass update info back and forward
This commit is contained in:
207
src/lib-python/src/asset.rs
Normal file
207
src/lib-python/src/asset.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use pyo3::prelude::*;
|
||||
|
||||
// Import your original structs from your rust library
|
||||
// Adjust the import path according to your crate structure
|
||||
use velopack::{VelopackAsset, UpdateInfo};
|
||||
|
||||
#[pyclass(name = "VelopackAsset")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PyVelopackAsset(pub VelopackAsset);
|
||||
|
||||
#[pymethods]
|
||||
impl PyVelopackAsset {
|
||||
#[new]
|
||||
#[pyo3(signature = (package_id=String::new(), version=String::new(), type_=String::new(), file_name=String::new(), sha1=String::new(), sha256=String::new(), size=0, notes_markdown=String::new(), notes_html=String::new()))]
|
||||
pub fn new(
|
||||
package_id: String,
|
||||
version: String,
|
||||
type_: String,
|
||||
file_name: String,
|
||||
sha1: String,
|
||||
sha256: String,
|
||||
size: u64,
|
||||
notes_markdown: String,
|
||||
notes_html: String,
|
||||
) -> Self {
|
||||
PyVelopackAsset(VelopackAsset {
|
||||
PackageId: package_id,
|
||||
Version: version,
|
||||
Type: type_,
|
||||
FileName: file_name,
|
||||
SHA1: sha1,
|
||||
SHA256: sha256,
|
||||
Size: size,
|
||||
NotesMarkdown: notes_markdown,
|
||||
NotesHtml: notes_html,
|
||||
})
|
||||
}
|
||||
|
||||
// Direct field access - much cleaner!
|
||||
#[getter]
|
||||
fn package_id(&self) -> &str {
|
||||
&self.0.PackageId
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn version(&self) -> &str {
|
||||
&self.0.Version
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn type_(&self) -> &str {
|
||||
&self.0.Type
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn file_name(&self) -> &str {
|
||||
&self.0.FileName
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn sha1(&self) -> &str {
|
||||
&self.0.SHA1
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn sha256(&self) -> &str {
|
||||
&self.0.SHA256
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn size(&self) -> u64 {
|
||||
self.0.Size
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn notes_markdown(&self) -> &str {
|
||||
&self.0.NotesMarkdown
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn notes_html(&self) -> &str {
|
||||
&self.0.NotesHtml
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"VelopackAsset(package_id='{}', version='{}', type='{}', file_name='{}', size={})",
|
||||
self.0.PackageId, self.0.Version, self.0.Type, self.0.FileName, self.0.Size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion traits for seamless interop
|
||||
impl From<VelopackAsset> for PyVelopackAsset {
|
||||
fn from(asset: VelopackAsset) -> Self {
|
||||
PyVelopackAsset(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PyVelopackAsset> for VelopackAsset {
|
||||
fn from(py_asset: PyVelopackAsset) -> Self {
|
||||
py_asset.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<VelopackAsset> for PyVelopackAsset {
|
||||
fn as_ref(&self) -> &VelopackAsset {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "UpdateInfo")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PyUpdateInfo(pub UpdateInfo);
|
||||
|
||||
#[pymethods]
|
||||
impl PyUpdateInfo {
|
||||
#[new]
|
||||
#[pyo3(signature = (target_full_release, base_release=None, deltas_to_target=Vec::new(), is_downgrade=false))]
|
||||
pub fn new(
|
||||
target_full_release: PyVelopackAsset,
|
||||
base_release: Option<PyVelopackAsset>,
|
||||
deltas_to_target: Vec<PyVelopackAsset>,
|
||||
is_downgrade: bool,
|
||||
) -> Self {
|
||||
PyUpdateInfo(UpdateInfo {
|
||||
TargetFullRelease: target_full_release.into(),
|
||||
BaseRelease: base_release.map(Into::into),
|
||||
DeltasToTarget: deltas_to_target.into_iter().map(Into::into).collect(),
|
||||
IsDowngrade: is_downgrade,
|
||||
})
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
pub fn new_full(target: PyVelopackAsset, is_downgrade: bool) -> PyUpdateInfo {
|
||||
PyUpdateInfo(UpdateInfo {
|
||||
TargetFullRelease: target.into(),
|
||||
BaseRelease: None,
|
||||
DeltasToTarget: Vec::new(),
|
||||
IsDowngrade: is_downgrade,
|
||||
})
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
pub fn new_delta(
|
||||
target: PyVelopackAsset,
|
||||
base: PyVelopackAsset,
|
||||
deltas: Vec<PyVelopackAsset>,
|
||||
) -> PyUpdateInfo {
|
||||
let rust_deltas = deltas.into_iter().map(Into::into).collect();
|
||||
PyUpdateInfo(UpdateInfo {
|
||||
TargetFullRelease: target.into(),
|
||||
BaseRelease: Some(base.into()),
|
||||
DeltasToTarget: rust_deltas,
|
||||
IsDowngrade: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn target_full_release(&self) -> PyVelopackAsset {
|
||||
PyVelopackAsset(self.0.TargetFullRelease.clone())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn base_release(&self) -> Option<PyVelopackAsset> {
|
||||
self.0.BaseRelease.clone().map(PyVelopackAsset)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn deltas_to_target(&self) -> Vec<PyVelopackAsset> {
|
||||
self.0.DeltasToTarget.iter().cloned().map(PyVelopackAsset).collect()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn is_downgrade(&self) -> bool {
|
||||
self.0.IsDowngrade
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"UpdateInfo(target_version='{}', has_base_release={}, deltas_count={}, is_downgrade={})",
|
||||
self.0.TargetFullRelease.Version,
|
||||
self.0.BaseRelease.is_some(),
|
||||
self.0.DeltasToTarget.len(),
|
||||
self.0.IsDowngrade
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UpdateInfo> for PyUpdateInfo {
|
||||
fn from(info: UpdateInfo) -> Self {
|
||||
PyUpdateInfo(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PyUpdateInfo> for UpdateInfo {
|
||||
fn from(py_info: PyUpdateInfo) -> Self {
|
||||
py_info.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<UpdateInfo> for PyUpdateInfo {
|
||||
fn as_ref(&self) -> &UpdateInfo {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,5 @@ impl VelopackError {
|
||||
fn __str__(&self) -> String {
|
||||
self.message.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use asset::PyUpdateInfo;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyModule;
|
||||
|
||||
mod asset;
|
||||
pub use asset::PyVelopackAsset;
|
||||
|
||||
|
||||
mod exceptions;
|
||||
pub use exceptions::VelopackError;
|
||||
|
||||
@@ -14,6 +19,10 @@ pub use manager::UpdateManagerWrapper;
|
||||
#[pymodule]
|
||||
fn velopack(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<VelopackError>()?;
|
||||
|
||||
m.add_class::<PyVelopackAsset>()?;
|
||||
m.add_class::<PyUpdateInfo>()?;
|
||||
|
||||
|
||||
|
||||
m.add_class::<VelopackAppWrapper>()?;
|
||||
|
||||
@@ -7,11 +7,11 @@ use velopack::{UpdateCheck, UpdateInfo, UpdateManager as VelopackUpdateManagerRu
|
||||
use velopack::sources::AutoSource;
|
||||
|
||||
use crate::exceptions::VelopackError;
|
||||
use crate::asset::PyUpdateInfo;
|
||||
|
||||
#[pyclass(name = "UpdateManager")]
|
||||
pub struct UpdateManagerWrapper {
|
||||
inner: VelopackUpdateManagerRust,
|
||||
updates: UpdateInfo,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -25,69 +25,80 @@ pub fn new(source: String) -> PyResult<Self> {
|
||||
.map_err(|e| PyErr::new::<VelopackError, _>(format!("Failed to create UpdateManager: {}", e)))?;
|
||||
Ok(UpdateManagerWrapper {
|
||||
inner,
|
||||
updates: UpdateInfo::default(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// check_for_updates return a bool indicating if updates are available
|
||||
/// This method checks for updates and returns true if updates are available, false otherwise.
|
||||
pub fn check_for_updates(&mut self) -> PyResult<bool> {
|
||||
match self.inner.check_for_updates() {
|
||||
Ok(UpdateCheck::UpdateAvailable(updates)) => {
|
||||
self.updates = updates;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(_) => {
|
||||
self.updates = UpdateInfo::default();
|
||||
Ok(false)
|
||||
}
|
||||
Err(e) => Err(PyErr::new::<VelopackError, _>(format!("Failed to check for updates: {}", e)))
|
||||
// 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<Option<PyUpdateInfo>> {
|
||||
let update_check = self.inner.check_for_updates()
|
||||
.map_err(|e| PyErr::new::<VelopackError, _>(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 = (progress_callback = None))]
|
||||
pub fn download_updates(&mut self, progress_callback: Option<PyObject>) -> PyResult<()> {
|
||||
if let Some(callback) = progress_callback {
|
||||
// Create a channel for progress updates
|
||||
let (sender, receiver) = mpsc::channel::<i16>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
#[pyo3(signature = (update_info, progress_callback = None))]
|
||||
pub fn download_updates(&mut self, update_info: &PyUpdateInfo, progress_callback: Option<PyObject>) -> 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::<i16>();
|
||||
|
||||
// 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(&self.updates, Some(sender))
|
||||
.map_err(|e| PyErr::new::<VelopackError, _>(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(&self.updates, None)
|
||||
.map_err(|e| PyErr::new::<VelopackError, _>(format!("Failed to download updates: {}", e)))
|
||||
.map(|_| ())
|
||||
}
|
||||
});
|
||||
|
||||
// Call download with the sender
|
||||
let result = self.inner.download_updates(&rust_update_info, Some(sender))
|
||||
.map_err(|e| PyErr::new::<VelopackError, _>(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::<VelopackError, _>(format!("Failed to download updates: {}", e)))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_updates_and_restart(&mut self) -> PyResult<()> {
|
||||
self.inner.apply_updates_and_restart(&self.updates)
|
||||
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::<VelopackError, _>(format!("Failed to apply updates and restart: {}", e)))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import velopack
|
||||
|
||||
import app_version
|
||||
@@ -8,8 +7,10 @@ version = app_version.version
|
||||
if __name__ == "__main__":
|
||||
velopack.App().run()
|
||||
um = velopack.UpdateManager("http://localhost:8080")
|
||||
if um.check_for_updates():
|
||||
um.download_updates()
|
||||
um.apply_updates_and_restart()
|
||||
update_info = um.check_for_updates()
|
||||
print(f"Update info: {update_info}")
|
||||
if update_info:
|
||||
um.download_updates(update_info)
|
||||
um.apply_updates_and_restart(update_info)
|
||||
with open("version_result.txt", "w") as f:
|
||||
f.write(f"{version}")
|
||||
Reference in New Issue
Block a user