Skip to content

Commit

Permalink
feat: Short circuiting check for empty trash
Browse files Browse the repository at this point in the history
`is_empty()` is a short circuiting function that checks if the trash is
empty on Freedesktop compatible systems and Windows.

The main purpose of `is_empty()` is to avoid evaluating the entire trash
context when the caller is only interested in whether the trash is empty
or not. This is especially useful for full trashes with many items.
  • Loading branch information
joshuamegnauth54 committed Oct 9, 2024
1 parent a2920fa commit 6d59fa9
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/freedesktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,32 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
Ok(result)
}

pub fn is_empty() -> Result<bool, Error> {
let trash_folders = trash_folders()?;

if trash_folders.is_empty() {
return Ok(true);
}

for folder in trash_folders {
// We're only concerned if the trash contains any files
// Therefore, we only need to check if the bin itself is empty
let bin = folder.join("files");
match bin.read_dir() {
Ok(mut entries) => {
if let Some(Ok(_)) = entries.next() {
return Ok(false);
}
}
Err(e) => {
warn!("The trash files folder {:?} could not be read. Error was {:?}", bin, e);
}
}
}

Ok(true)
}

pub fn trash_folders() -> Result<HashSet<PathBuf>, Error> {
let EvaluatedTrashFolders { trash_folders, home_error, .. } = eval_trash_folders()?;

Expand Down
18 changes: 18 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,24 @@ pub mod os_limited {
platform::list()
}

/// Returns whether the trash is empty or has at least one item.
///
/// Unlike calling [`list`], this function short circuits without evaluating every item.
///
/// # Example
///
/// ```
/// use trash::os_limited::is_empty;
/// if is_empty().unwrap_or(true) {
/// println!("Trash is empty");
/// } else {
/// println!("Trash contains at least one item");
/// }
/// ```
pub fn is_empty() -> Result<bool, Error> {
platform::is_empty()
}

/// Returns all valid trash bins on supported Unix platforms.
///
/// Valid trash folders include the user's personal "home trash" as well as designated trash
Expand Down
10 changes: 10 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,14 @@ mod os_limited {
_ => panic!("restore_all was expected to return `trash::ErrorKind::RestoreTwins` but did not."),
}
}

#[test]
#[serial]
fn is_empty_matches_list() {
init_logging();

let is_empty_list = trash::os_limited::list().unwrap().is_empty();
let is_empty = trash::os_limited::is_empty().unwrap();
assert_eq!(is_empty, is_empty_list, "is_empty() should match empty status from list()");
}
}
15 changes: 15 additions & 0 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
}
}

pub fn is_empty() -> Result<bool, Error> {
ensure_com_initialized();
unsafe {
let recycle_bin: IShellItem =
SHGetKnownFolderItem(&FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT, HANDLE::default())?;
let pesi: IEnumShellItems = recycle_bin.BindToHandler(None, &BHID_EnumItems)?;

let mut count = 0u32;
let mut items = [None];
pesi.Next(&mut items, Some(&mut count as *mut u32))?;

Ok(count == 0)
}
}

pub fn metadata(item: &TrashItem) -> Result<TrashItemMetadata, Error> {
ensure_com_initialized();
let id_as_wide = to_wide_path(&item.id);
Expand Down

0 comments on commit 6d59fa9

Please sign in to comment.