mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add file-based locking to rust library
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -865,6 +865,16 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fslock"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -2186,6 +2196,7 @@ dependencies = [
|
||||
"async-std",
|
||||
"bitflags 2.6.0",
|
||||
"derivative",
|
||||
"fslock",
|
||||
"glob",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
||||
@@ -78,6 +78,7 @@ filelocksmith = "0.1"
|
||||
image = { version = "0.25", default-features = false, features = ["gif", "jpeg", "png"] }
|
||||
fs_extra = "1.3"
|
||||
memmap2 = "0.9"
|
||||
fslock = "0.2"
|
||||
|
||||
# default to small, optimized workspace release binaries
|
||||
[profile.release]
|
||||
|
||||
@@ -21,6 +21,7 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
info!("Reading package manifest...");
|
||||
let app = pkg.read_manifest()?;
|
||||
|
||||
|
||||
info!("Package manifest loaded successfully.");
|
||||
info!(" Package ID: {}", &app.id);
|
||||
info!(" Package Version: {}", &app.version);
|
||||
@@ -30,8 +31,6 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
info!(" Package Machine Architecture: {}", &app.machine_architecture);
|
||||
info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies);
|
||||
|
||||
let _mutex = shared::retry_io(|| windows::create_global_mutex(&app.id))?;
|
||||
|
||||
if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? {
|
||||
info!("Cancelling setup. Pre-requisites not installed.");
|
||||
return Ok(());
|
||||
@@ -115,6 +114,11 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
info!("Preparing and cleaning installation directory...");
|
||||
remove_dir_all::ensure_empty_dir(&root_path)?;
|
||||
|
||||
info!("Acquiring lock...");
|
||||
let paths = create_config_from_root_dir(&root_path);
|
||||
let locator = VelopackLocator::new(paths, app);
|
||||
let _mutex = locator.try_get_exclusive_lock()?;
|
||||
|
||||
let tx = if dialogs::get_silent() {
|
||||
info!("Will not show splash because silent mode is on.");
|
||||
let (tx, _) = std::sync::mpsc::channel::<i16>();
|
||||
@@ -122,10 +126,10 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
} else {
|
||||
info!("Reading splash image...");
|
||||
let splash_bytes = pkg.get_splash_bytes();
|
||||
windows::splash::show_splash_dialog(app.title.to_owned(), splash_bytes)
|
||||
windows::splash::show_splash_dialog(locator.get_manifest_title(), splash_bytes)
|
||||
};
|
||||
|
||||
let install_result = install_impl(pkg, &root_path, &tx, start_args);
|
||||
let install_result = install_impl(pkg, &locator, &tx, start_args);
|
||||
let _ = tx.send(windows::splash::MSG_CLOSE);
|
||||
|
||||
if install_result.is_ok() {
|
||||
@@ -148,13 +152,9 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_impl(pkg: &mut BundleZip, root_path: &PathBuf, tx: &std::sync::mpsc::Sender<i16>, start_args: Option<Vec<&str>>) -> Result<()> {
|
||||
fn install_impl(pkg: &mut BundleZip, locator: &VelopackLocator, tx: &std::sync::mpsc::Sender<i16>, start_args: Option<Vec<&str>>) -> Result<()> {
|
||||
info!("Starting installation!");
|
||||
|
||||
let app_manifest = pkg.read_manifest()?;
|
||||
let paths = create_config_from_root_dir(root_path);
|
||||
let locator = VelopackLocator::new(paths, app_manifest);
|
||||
|
||||
// all application paths
|
||||
let updater_path = locator.get_update_path();
|
||||
let packages_path = locator.get_packages_dir();
|
||||
|
||||
@@ -146,34 +146,31 @@ fn try_legacy_migration(root_dir: &PathBuf, manifest: &Manifest) -> Result<Velop
|
||||
// if started by legacy Squirrel, the working dir of Update.exe may be inside the app-* folder,
|
||||
// meaning we can not clean up properly.
|
||||
std::env::set_current_dir(&root_dir)?;
|
||||
|
||||
let _mutex = shared::retry_io(|| crate::windows::create_global_mutex(&manifest.id))?;
|
||||
let path_config = locator::create_config_from_root_dir(root_dir);
|
||||
|
||||
let package = locator::find_latest_full_package(&path_config.PackagesDir).ok_or_else(|| anyhow!("Unable to find latest full package."))?;
|
||||
|
||||
warn!("This application is installed in a folder prefixed with 'app-'. Attempting to migrate...");
|
||||
let _ = shared::force_stop_package(&root_dir);
|
||||
|
||||
let current_dir = &path_config.CurrentBinaryDir;
|
||||
if !Path::new(¤t_dir).exists() {
|
||||
// reset current manifest shortcuts, so when the new manifest is being read
|
||||
// new shortcuts will be force-created
|
||||
let mut modified_manifest = manifest.clone();
|
||||
modified_manifest.shortcut_locations = String::new();
|
||||
let locator = VelopackLocator::new(path_config, modified_manifest);
|
||||
let _mutex = locator.try_get_exclusive_lock()?;
|
||||
|
||||
if !locator.get_current_bin_dir().exists() {
|
||||
info!("Renaming latest app-* folder to current.");
|
||||
if let Some((latest_app_dir, _latest_ver)) = shared::get_latest_app_version_folder(&root_dir)? {
|
||||
fs::rename(latest_app_dir, ¤t_dir)?;
|
||||
fs::rename(latest_app_dir, locator.get_current_bin_dir())?;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Removing old shortcuts...");
|
||||
win::remove_all_shortcuts_for_root_dir(&root_dir);
|
||||
|
||||
// reset current manifest shortcuts, so when the new manifest is being read
|
||||
// new shortcuts will be force-created
|
||||
let mut modified_manifest = manifest.clone();
|
||||
modified_manifest.shortcut_locations = String::new();
|
||||
|
||||
info!("Applying latest full package...");
|
||||
let buf = Path::new(&package.0).to_path_buf();
|
||||
let locator = VelopackLocator::new(path_config, modified_manifest);
|
||||
let new_locator = super::apply(&locator, false, OperationWait::NoWait, Some(&buf), None, false)?;
|
||||
|
||||
info!("Removing old app-* folders...");
|
||||
|
||||
@@ -189,8 +189,7 @@ fn apply(matches: &ArgMatches) -> Result<()> {
|
||||
info!(" Exe Args: {:?}", exe_args);
|
||||
|
||||
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let _mutex = shared::retry_io(|| windows::create_global_mutex(&locator.get_manifest_id()))?;
|
||||
let _mutex = locator.get_exclusive_lock_blocking()?;
|
||||
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -78,21 +78,6 @@ impl Drop for MutexDropGuard {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_global_mutex(app_id: &str) -> Result<MutexDropGuard> {
|
||||
let mutex_name = format!("velopack-{}", app_id);
|
||||
info!("Attempting to open global system mutex: '{}'", &mutex_name);
|
||||
let encodedu16 = super::strings::string_to_u16(mutex_name);
|
||||
let encoded = PCWSTR(encodedu16.as_ptr());
|
||||
let mutex = unsafe { CreateMutexW(None, true, encoded) }?;
|
||||
match unsafe { GetLastError() } {
|
||||
Foundation::ERROR_SUCCESS => Ok(MutexDropGuard { mutex }),
|
||||
Foundation::ERROR_ALREADY_EXISTS => {
|
||||
Err(anyhow!("Another installer or updater for this application is running, quit that process and try again."))
|
||||
}
|
||||
err => Err(anyhow!("Unable to create global mutex. Error code {:?}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_environment_strings<P: AsRef<str>>(input: P) -> Result<String> {
|
||||
use windows::Win32::System::Environment::ExpandEnvironmentStringsW;
|
||||
let encoded_u16 = super::strings::string_to_u16(input);
|
||||
|
||||
@@ -46,6 +46,7 @@ rand.workspace = true
|
||||
native-tls.workspace = true
|
||||
sha1.workspace = true
|
||||
sha2.workspace = true
|
||||
fslock.workspace = true
|
||||
|
||||
# typescript
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
@@ -275,6 +275,33 @@ impl VelopackLocator {
|
||||
self.paths.IsPortable
|
||||
}
|
||||
|
||||
/// Attemps to open / lock a file in the app's package directory for exclusive write access.
|
||||
/// Fails immediately if the lock cannot be acquired.
|
||||
pub fn try_get_exclusive_lock(&self) -> Result<fslock::LockFile, Error> {
|
||||
info!("Attempting to acquire exclusive lock on packages directory (non-blocking)...");
|
||||
let packages_dir = self.get_packages_dir();
|
||||
std::fs::create_dir_all(&packages_dir)?;
|
||||
let lock_file_path = packages_dir.join(".velopack_lock");
|
||||
let mut file = fslock::LockFile::open(&lock_file_path)?;
|
||||
let result = file.try_lock_with_pid()?;
|
||||
if !result {
|
||||
return Err(Error::Generic("Could not acquire exclusive lock on packages directory".to_owned()));
|
||||
}
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
/// Attemps to open / lock a file in the app's package directory for exclusive write access.
|
||||
/// Blocks until the lock can be acquired.
|
||||
pub fn get_exclusive_lock_blocking(&self) -> Result<fslock::LockFile, Error> {
|
||||
info!("Attempting to acquire exclusive lock on packages directory (blocking)...");
|
||||
let packages_dir = self.get_packages_dir();
|
||||
std::fs::create_dir_all(&packages_dir)?;
|
||||
let lock_file_path = packages_dir.join(".velopack_lock");
|
||||
let mut file = fslock::LockFile::open(&lock_file_path)?;
|
||||
file.lock_with_pid()?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
fn path_as_string(path: &PathBuf) -> String {
|
||||
path.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ impl UpdateManager {
|
||||
/// - 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> {
|
||||
let _mutex = &self.locator.try_get_exclusive_lock()?;
|
||||
let name = &update.TargetFullRelease.FileName;
|
||||
let packages_dir = &self.locator.get_packages_dir();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user