-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Bundles #145
Comments
Hi @hacknus, you could use the individual download, extract, move, self_replace utilities instead of the all-in-one update API. There's a brief example at the end of the readme. For os-specific installations, you will likely need to have three different implementations and use a compile-time |
Thanks, I managed to create an update method that takes care of this. I had to implement the unzipping myself without using the built-in of Would you be open to a PR introducing an unzipping method that extracts more than one file? I could then put this update function for bundles in the
use self_update::self_replace;
use self_update::update::Release;
use semver::Version;
use std::fs::File;
use std::path::Path;
use std::process::Command;
use std::{env, fs, io};
use zip::ZipArchive;
const REPO_OWNER: &str = "hacknus";
const REPO_NAME: &str = "serial-monitor-rust";
const MACOS_APP_NAME: &str = "Serial Monitor.app";
pub fn restart_application() {
// Get the current executable path
let current_exe = std::env::current_exe().expect("Failed to get current executable path");
// Launch a new instance of the application
let _ = Command::new(current_exe).spawn();
}
fn extract_zip(tmp_archive_path: &Path, tmp_archive_dir: &Path) -> io::Result<()> {
// Open the zip file
let file = File::open(tmp_archive_path)?;
let mut archive = ZipArchive::new(file)?;
// Iterate through the entries in the zip file
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = Path::new(tmp_archive_dir).join(file.name());
if file.is_dir() {
// Create directories
std::fs::create_dir_all(&outpath)?;
} else {
// If the parent directory doesn't exist, create it
if let Some(parent) = outpath.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
// Write the file to disk
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
// Set file permissions if needed
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
}
}
}
Ok(())
}
fn copy_dir(src: &Path, dest: &Path, binary_name: &str) -> io::Result<()> {
// Ensure the destination directory exists
if !dest.exists() {
fs::create_dir_all(dest)?;
}
// Iterate through entries in the source directory
for entry in fs::read_dir(src)? {
let entry = entry?;
let path = entry.path();
let dest_path = dest.join(entry.file_name());
if path.is_dir() {
// Recursively copy subdirectories
copy_dir(&path, &dest_path, binary_name)?;
} else if let Some(file_name) = path.file_name() {
if file_name != binary_name {
// Copy files except for the binary
fs::copy(&path, &dest_path)?;
}
}
}
Ok(())
}
pub fn check_update() -> Option<Release> {
if let Ok(builder) = self_update::backends::github::ReleaseList::configure()
.repo_owner(REPO_OWNER)
.repo_name(REPO_NAME)
.build()
{
if let Ok(releases) = builder.fetch() {
let current_version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
return releases
.iter()
.filter_map(|release| {
let release_version_str = release
.version
.strip_prefix("v")
.unwrap_or(&release.version);
Version::parse(release_version_str)
.ok()
.map(|parsed_version| (parsed_version, release))
})
.filter(|(parsed_version, _)| parsed_version > ¤t_version) // Compare versions
.max_by(|(a, _), (b, _)| a.cmp(b)) // Find the max version
.map(|(_, release)| release.clone()); // Return the release
}
}
None
}
pub fn update(release: Release) -> Result<(), Box<dyn std::error::Error>> {
// this if clause is only required if .msi and .exe are available for windows and .deb and a binary executable are available for linux
let target_asset = if cfg!(target_os = "windows") {
release
.asset_for(self_update::get_target(), Some("exe"))
.unwrap()
} else if cfg!(target_os = "linux") {
release
.asset_for(self_update::get_target(), Some("bin"))
.unwrap()
} else {
release.asset_for(self_update::get_target(), None).unwrap()
};
let tmp_archive_dir = tempfile::TempDir::new()?;
let tmp_archive_path = tmp_archive_dir.path().join(&target_asset.name);
let tmp_archive = fs::File::create(&tmp_archive_path)?;
self_update::Download::from_url(&target_asset.download_url)
.set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
.download_to(&tmp_archive)?;
extract_zip(&tmp_archive_path, tmp_archive_dir.path())?;
let new_exe = if cfg!(target_os = "windows") {
let binary = env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
tmp_archive_dir.path().join(binary)
} else if cfg!(target_os = "macos") {
let binary = env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
let app_dir = env::current_exe()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf();
let app_name = app_dir
.clone()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
let _ = copy_dir(&tmp_archive_dir.path().join(&app_name), &app_dir, &binary);
// MACOS_APP_NAME either needs to be hardcoded or extracted from the downloaded and
// extracted archive, but we cannot just assume that the parent directory of the
// currently running executable is equal to the app name - this is especially not
// the case if we run the code with cargo run.
tmp_archive_dir
.path()
.join(format!("{}/Contents/MacOS/{}", MACOS_APP_NAME, binary))
} else if cfg!(target_os = "linux") {
let binary = env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
tmp_archive_dir.path().join(binary)
} else {
panic!("Running on an unsupported OS");
};
self_replace::self_replace(new_exe)?;
Ok(())
} |
Nice! A PR is welcome |
First of all, thank you very much for the work on this cool and very useful project!
I am running a GUI on macOS. It is bundled like this:
The release will be downloaded as
ApplicationName.app.zip
, howeverself-update
fails to unpack it, because it expects a single file and not a directory structure. Is there a way to work with this?Similarly: My application is also distributed as
.deb
for linux and.msi
for windows - how would that work withself-update
?The text was updated successfully, but these errors were encountered: