Skip to content

Commit

Permalink
feat(libscoop|sync): basic support of uninstalling packages
Browse files Browse the repository at this point in the history
What the `basic support` means is with this commit only packages
without using PowerShell scripts (`pre_uninstall`, `uninstaller`
and `post_uninstall`) are supported.

Signed-off-by: Chawye Hsu <[email protected]>
  • Loading branch information
chawyehsu committed Aug 8, 2023
1 parent 4bae700 commit b1f0f6b
Show file tree
Hide file tree
Showing 14 changed files with 632 additions and 28 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/libscoop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dirs = "5.0.1"
flume = "0.10"
futures = { version = "0.3", features = ["thread-pool"] }
git2 = "0.17.2"
junction = "1.0.0"
log = "0.4"
once_cell = "1.18.0"
rayon = "1.7.0"
Expand Down
60 changes: 60 additions & 0 deletions crates/libscoop/src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::{error::Fallible, internal, package::Package, Event, Session};

/// Unset all environment variables defined by a given package.
pub fn remove(session: &Session, package: &Package) -> Fallible<()> {
assert!(package.is_installed());

// Unset environment variables
if let Some(env_set) = package.manifest().env_set() {
if let Some(tx) = session.emitter() {
let _ = tx.send(Event::PackageEnvVarRemoveStart);
}

let keys = env_set.keys();
for key in keys {
internal::env::set(key, "")?;
}

if let Some(tx) = session.emitter() {
let _ = tx.send(Event::PackageEnvVarRemoveDone);
}
}

// Remove environment path
if let Some(env_add_path) = package.manifest().env_add_path() {
let mut env_path_list = internal::env::get_env_path_list()?;
let config = session.config();
let mut app_path = config.root_path().join("apps");
app_path.push(package.name());

if let Some(tx) = session.emitter() {
let _ = tx.send(Event::PackageEnvPathRemoveStart);
}

let version = if config.no_junction() {
package.installed_version().unwrap()
} else {
"current"
};

let paths = env_add_path
.into_iter()
.map(|p| {
internal::path::normalize_path(app_path.join(version).join(p))
.to_str()
.unwrap()
.to_owned()
})
.collect::<Vec<_>>();

env_path_list.retain(|p| !paths.contains(p));

internal::env::set("PATH", &env_path_list.join(";"))?;

if let Some(tx) = session.emitter() {
let _ = tx.send(Event::PackageEnvPathRemoveDone);
}
}

Ok(())
}
73 changes: 59 additions & 14 deletions crates/libscoop/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,43 +69,88 @@ pub enum Event {
/// Bucket update has made some progress.
BucketUpdateProgress(BucketUpdateProgressContext),

/// Bucket update is finished.
/// Bucket update has finished.
BucketUpdateDone,

/// Package resolving is started.
PackageResolveStart,
/// Package has started to be committed.
PackageCommitStart(String),

/// Package resolving is finished.
PackageResolveDone,
/// Package has been committed.
PackageCommitDone(String),

/// Calculating download size is started.
/// Calculating download size has started.
PackageDownloadSizingStart,

/// Calculating download size is finished.
/// Calculating download size has finished.
PackageDownloadSizingDone,

/// Package download is started.
/// Package download has started.
PackageDownloadStart,

/// Package download has made some progress.
PackageDownloadProgress(PackageDownloadProgressContext),

/// Package download is finished.
/// Package download has finished.
PackageDownloadDone,

/// Package integrity check is started.
/// Package environment path(s) removal has started.
PackageEnvPathRemoveStart,

/// Package environment path(s) removal has finished.
PackageEnvPathRemoveDone,

/// Package environment variable(s) removal has started.
PackageEnvVarRemoveStart,

/// Package environment variable(s) removal has finished.
PackageEnvVarRemoveDone,

/// Package integrity check has started.
PackageIntegrityCheckStart,

/// Package integrity check has made some progress.
PackageIntegrityCheckProgress(String),

/// Package integrity check is finished.
/// Package integrity check has finished.
PackageIntegrityCheckDone,

/// Package is started to be committed.
PackageCommitStart(String),
/// Package persist removal has started.
PackagePersistPurgeStart,

/// Package persist removal has finished.
PackagePersistPurgeDone,

/// Package PowerShell module removal has started.
PackagePsModuleRemoveStart(String),

/// Package PowerShell module removal has finished.
PackagePsModuleRemoveDone,

/// Package resolving has started.
PackageResolveStart,

/// Package resolving has finished.
PackageResolveDone,

/// Package shim removal has started.
PackageShimRemoveStart,

/// Package shim removal has made some progress.
PackageShimRemoveProgress(String),

/// Package shim removal has finished.
PackageShimRemoveDone,

/// Package shortcut removal has started.
PackageShortcutRemoveStart,

/// Package shortcut removal has made some progress.
PackageShortcutRemoveProgress(String),

/// Package shortcut removal has finished.
PackageShortcutRemoveDone,

/// Package sync operation is finished.
/// Package sync operation has finished.
PackageSyncDone,

/// Prompt the user to confirm the transaction.
Expand Down
14 changes: 13 additions & 1 deletion crates/libscoop/src/internal/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,21 @@ pub fn set(key: &str, value: &str) -> Fallible<()> {
let (env, _) = HKCU.create_subkey(path)?;

if value.is_empty() {
env.delete_value(key)?;
// ignore error of deleting non-existent value
let _ = env.delete_value(key);
} else {
env.set_value(key, &value)?;
}
Ok(())
}

/// Get the value of the `PATH` environment variable as a list of paths.
pub fn get_env_path_list() -> Fallible<Vec<String>> {
let env_path = get("PATH")?;
Ok(env_path
.into_string()
.unwrap()
.split(';')
.map(|s| s.to_owned())
.collect())
}
54 changes: 52 additions & 2 deletions crates/libscoop/src/internal/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ pub fn ensure_dir<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<()> {
std::fs::create_dir_all(path.as_ref())
}

pub fn remove_dir<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<()> {
remove_dir_all::remove_dir_all(path.as_ref())
/// Remove given `path` recursively.
pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
remove_dir_all::remove_dir_all(path)
}

/// Remove all files and subdirectories in given `path`.
Expand Down Expand Up @@ -83,3 +84,52 @@ where
.open(path)?;
Ok(serde_json::to_writer_pretty(file, &data)?)
}

/// Remove a symlink at `lnk`.
pub fn remove_symlink<P: AsRef<Path>>(lnk: P) -> io::Result<()> {
let lnk = lnk.as_ref();
let metadata = lnk.symlink_metadata()?;
let mut permissions = metadata.permissions();

// Remove possible readonly flag on the symlink added by `attrib +R` command
if permissions.readonly() {
// Remove readonly flag
permissions.set_readonly(false);
std::fs::set_permissions(lnk, permissions)?;
}

// We knew that `lnk` is a symlink but we don't know if it is a file or a
// directory. So we need to check its metadata to determine how to remove
// it. The file type of the symlink itself is always `FileType::Symlink`
// and `symlink_metadata::is_dir` always returns `false` for symlinks, so
// we have to check the metadata of the target file.
let target_metadata = lnk.metadata()?;

if target_metadata.is_dir() {
std::fs::remove_dir(lnk)
} else {
std::fs::remove_file(lnk)
}
}

/// Create a directory symlink at `lnk` pointing to `src`.
pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, lnk: Q) -> io::Result<()> {
// It is possible to create a symlink on Windows, but one of the following
// conditions must be met:
//
// Either: the process has the `SeCreateSymbolicLinkPrivilege` privilege,
// or: the OS is Windows 10 Creators Update or later and Developer Mode
// enabled.
//
// We prefer symlink over junction because:
// https://stackoverflow.com/questions/9042542/what-is-the-difference-between-ntfs-junction-points-and-symbolic-links
//
// Here we try to create a symlink first, and if it fails, we try to create
// a junction which does not require any special privilege and works on
// older versions of Windows.
if std::os::windows::fs::symlink_dir(src.as_ref(), lnk.as_ref()).is_err() {
junction::create(src, lnk)
} else {
Ok(())
}
}
5 changes: 5 additions & 0 deletions crates/libscoop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ mod bucket;
mod cache;
mod config;
mod constant;
mod env;
mod error;
mod event;
mod internal;
mod package;
mod persist;
mod psmodule;
mod session;
mod shim;
mod shortcut;

pub mod operation;

Expand Down
Loading

0 comments on commit b1f0f6b

Please sign in to comment.