Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,136 @@ fn update() -> Result<(), Box<dyn std::error::Error>> {
}
```

In case the application has been packaged as a bundle (using `cargo bundle` e.g. on macOS as `Application.app`), the following example can be used:
Note that for zipped releases, the deflate feature is required:
```toml
features = ["archive-zip", "compression-zip-deflate"]
```
```rust
const MACOS_APP_NAME: &str = "Application.app";

/// method to copy the complete directory `src` to `dest` but skipping the binary `binary_name`
/// since we have to use `self-replace` for that.
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(())
}

/// custom update function for use with bundles
pub fn update(release: Release) -> Result<(), Box<dyn std::error::Error>> {
let releases = self_update::backends::github::ReleaseList::configure()
.repo_owner("jaemk")
.repo_name("self_update")
.build()?
.fetch()?;
println!("found releases:");
println!("{:#?}\n", releases);

// get the first available release
let asset = releases[0]
.asset_for(&self_update::get_target(), None)
.unwrap();

let tmp_archive_dir = tempfile::TempDir::new()?;
let tmp_archive_path = tmp_archive_dir.path().join(&asset.name);
let tmp_archive = fs::File::create(&tmp_archive_path)?;

self_update::Download::from_url(&asset.download_url)
.set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
.download_to(&tmp_archive)?;

self_update::Extract::from_source(&tmp_archive_path).extract_into(tmp_archive_dir.path())?;
let new_exe = if cfg!(target_os = "windows") {
// only get the exe path on 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") {
// get the binary path on macOS
let binary = env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();

// get the parent directory of the `Application.app` bundle
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") {
// only get the binary path from 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");
};

// replace as usual
self_replace::self_replace(new_exe)?;
Ok(())
}


```

### Troubleshooting

When using cross compilation tools such as cross if you want to use rustls and not openssl
Expand Down
40 changes: 27 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ self_update = { version = "0.27.0", features = ["rustls"], default-features = fa

*/

#[macro_use]
extern crate log;
pub use self_replace;
pub use tempfile::TempDir;

Expand All @@ -148,13 +150,11 @@ use std::fs;
use std::io;
use std::path;

#[macro_use]
extern crate log;

#[macro_use]
mod macros;
pub mod backends;
pub mod errors;
pub mod restart;
pub mod update;
pub mod version;

Expand Down Expand Up @@ -375,8 +375,6 @@ impl<'a> Extract<'a> {
None => detect_archive(self.source)?,
};

// We cannot use a feature flag in a match arm. To bypass this the code block is
// isolated in a closure and called accordingly.
let extract_into_plain_or_tar = |source: fs::File, compression: Option<Compression>| {
let mut reader = Self::get_archive_reader(source, compression);

Expand Down Expand Up @@ -427,21 +425,37 @@ impl<'a> Extract<'a> {
let mut archive = zip::ZipArchive::new(source)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;

let output_path = into_dir.join(file.name());
if let Some(parent_dir) = output_path.parent() {
if let Err(e) = fs::create_dir_all(parent_dir) {
if e.kind() != io::ErrorKind::AlreadyExists {
return Err(Error::Io(e));
let outpath = into_dir.join(file.name());

if file.is_dir() {
// Create directories
fs::create_dir_all(&outpath).map_err(Error::Io)?;
} else {
// Create parent directories if they don't exist
if let Some(parent) = outpath.parent() {
if !parent.exists() {
fs::create_dir_all(parent).map_err(Error::Io)?;
}
}

// Write the file to disk
let mut outfile = fs::File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}

let mut output = fs::File::create(output_path)?;
io::copy(&mut file, &mut output)?;
// Set file permissions (only on Unix)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))
.map_err(Error::Io)?;
}
}
}
}
};

Ok(())
}

Expand Down
10 changes: 10 additions & 0 deletions src/restart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::process::Command;

pub fn restart() {
// 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 but do not wait for the command to complete!
#[allow(clippy::zombie_processes)]
let _ = Command::new(current_exe).spawn();
}