-
Notifications
You must be signed in to change notification settings - Fork 35
Return trash items that are created by delete #109
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,12 +33,13 @@ impl PlatformTrashContext { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl TrashContext { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec<PathBuf>) -> Result<(), Error> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec<PathBuf>) -> Result<Option<Vec<TrashItem>>, Error> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let home_trash = home_trash()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sorted_mount_points = get_sorted_mount_points()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let home_topdir = home_topdir(&sorted_mount_points)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| debug!("The home topdir is {:?}", home_topdir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let uid = unsafe { libc::getuid() }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut items = Vec::with_capacity(full_paths.len()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for path in full_paths { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| debug!("Deleting {:?}", path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let topdir = get_first_topdir_containing_path(&path, &sorted_mount_points); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -47,18 +48,19 @@ impl TrashContext { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| debug!("The topdir was identical to the home topdir, so moving to the home trash."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note that the following function creates the trash folder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and its required subfolders in case they don't exist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| move_to_trash(path, &home_trash, topdir).map_err(|(p, e)| fs_error(p, e))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| items.push(move_to_trash(path, &home_trash, topdir).map_err(|(p, e)| fs_error(p, e))?); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if topdir.to_str() == Some("/var/home") && home_topdir.to_str() == Some("/") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| debug!("The topdir is '/var/home' but the home_topdir is '/', moving to the home trash anyway."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| move_to_trash(path, &home_trash, topdir).map_err(|(p, e)| fs_error(p, e))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| items.push(move_to_trash(path, &home_trash, topdir).map_err(|(p, e)| fs_error(p, e))?); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| execute_on_mounted_trash_folders(uid, topdir, true, true, |trash_path| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| move_to_trash(&path, trash_path, topdir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| items.push(move_to_trash(&path, trash_path, topdir)?); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|(p, e)| fs_error(p, e))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(Some(items)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -450,7 +452,7 @@ fn move_to_trash( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| src: impl AsRef<Path>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trash_folder: impl AsRef<Path>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _topdir: impl AsRef<Path>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<(), FsError> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<TrashItem, FsError> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let src = src.as_ref(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let trash_folder = trash_folder.as_ref(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let files_folder = trash_folder.join("files"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -491,6 +493,7 @@ fn move_to_trash( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| info_name.push(".trashinfo"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let info_file_path = info_folder.join(&info_name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let info_result = OpenOptions::new().create_new(true).write(true).open(&info_file_path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut time_deleted = -1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match info_result { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if error.kind() == std::io::ErrorKind::AlreadyExists { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -510,10 +513,12 @@ fn move_to_trash( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[cfg(feature = "chrono")] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let now = chrono::Local::now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time_deleted = now.timestamp(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| writeln!(file, "DeletionDate={}", now.format("%Y-%m-%dT%H:%M:%S")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[cfg(not(feature = "chrono"))] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time_deleted = -1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -537,12 +542,18 @@ fn move_to_trash( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(_) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // We did it! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(TrashItem { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: info_file_path.into(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: filename.into(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original_parent: src | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .parent() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect("Absolute path to trashed item should have a parent") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .to_path_buf(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+545
to
+551
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(TrashItem { | |
| id: info_file_path.into(), | |
| name: filename.into(), | |
| original_parent: src | |
| .parent() | |
| .expect("Absolute path to trashed item should have a parent") | |
| .to_path_buf(), | |
| // Normally, an absolute path to a trashed item should always have a parent. | |
| // However, in case a root path is ever passed here, handle it gracefully | |
| // instead of panicking, and return an explicit error. | |
| let original_parent = match src.parent() { | |
| Some(parent) => parent.to_path_buf(), | |
| None => { | |
| return Err(( | |
| src.to_path_buf(), | |
| std::io::Error::new( | |
| std::io::ErrorKind::Other, | |
| "Trashed item path has no parent directory", | |
| ), | |
| )); | |
| } | |
| }; | |
| return Ok(TrashItem { | |
| id: info_file_path.into(), | |
| name: filename.into(), | |
| original_parent, |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -81,7 +81,7 @@ impl TrashContext { | |||||||||||||||||||||||
| /// trash::delete("delete_me").unwrap(); | ||||||||||||||||||||||||
| /// assert!(File::open("delete_me").is_err()); | ||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||
| pub fn delete<T: AsRef<Path>>(&self, path: T) -> Result<(), Error> { | ||||||||||||||||||||||||
| pub fn delete<T: AsRef<Path>>(&self, path: T) -> Result<Option<Vec<TrashItem>>, Error> { | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| pub fn delete<T: AsRef<Path>>(&self, path: T) -> Result<Option<Vec<TrashItem>>, Error> { | |
| pub fn delete<T: AsRef<Path>>(&self, path: T) -> Result<(), Error> { | |
| self.delete_all(&[path])?; | |
| Ok(()) | |
| } | |
| /// Like [`delete`], but returns additional information about the trashed items. | |
| pub fn delete_with_info<T: AsRef<Path>>( | |
| &self, | |
| path: T, | |
| ) -> Result<Option<Vec<TrashItem>>, Error> { |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking API change that affects the return type of the delete_all function. All existing users of this library will need to update their code to handle the new Option<Vec<TrashItem>> return value instead of (). Consider using a major version bump when releasing this change, or alternatively, providing a new method name (e.g., delete_all_with_info) alongside the existing one to maintain backward compatibility.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -297,4 +297,36 @@ mod os_limited { | |||||||||||||||||||||||||||||
| let is_empty = trash::os_limited::is_empty().unwrap(); | ||||||||||||||||||||||||||||||
| assert_eq!(is_empty, is_empty_list, "is_empty() should match empty status from list()"); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] | ||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||
| #[serial] | ||||||||||||||||||||||||||||||
| fn delete_items_info() { | ||||||||||||||||||||||||||||||
| init_logging(); | ||||||||||||||||||||||||||||||
| let names = { | ||||||||||||||||||||||||||||||
| let prefix = get_unique_name(); | ||||||||||||||||||||||||||||||
| let mut names = Vec::with_capacity(5); | ||||||||||||||||||||||||||||||
| for n in 0..4 { | ||||||||||||||||||||||||||||||
| let name = format!("{prefix}#{n}").into_bytes(); | ||||||||||||||||||||||||||||||
| names.push(OsString::from_vec(name)); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Throw in an invalid UTF-8 OsString for good measure | ||||||||||||||||||||||||||||||
| let mut name = OsStr::new(&format!("{prefix}#")).to_os_string().into_encoded_bytes(); | ||||||||||||||||||||||||||||||
| name.push(168); | ||||||||||||||||||||||||||||||
| names.push(OsString::from_vec(name)); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| names | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| for name in &names { | ||||||||||||||||||||||||||||||
| File::create_new(name).unwrap(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let deleted_names = trash::delete_all(&names).unwrap().expect("Should get a list of deleted items"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| let deleted_names = trash::delete_all(&names).unwrap().expect("Should get a list of deleted items"); | |
| let deleted_names = trash::delete_all(&names) | |
| .unwrap() | |
| .expect("Expected Some(deleted_items) from trash::delete_all(&names) on freedesktop/unix platform, but got None"); |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable is named deleted_names but it contains TrashItem objects, not just names. Consider renaming to deleted_items or items to better reflect its contents.
| let deleted_names = trash::delete_all(&names).unwrap().expect("Should get a list of deleted items"); | |
| assert_eq!(deleted_names.len(), names.len()); | |
| for name in names { | |
| assert!(deleted_names.iter().any(|item| item.name == name)); | |
| let deleted_items = trash::delete_all(&names).unwrap().expect("Should get a list of deleted items"); | |
| assert_eq!(deleted_items.len(), names.len()); | |
| for name in names { | |
| assert!(deleted_items.iter().any(|item| item.name == name)); |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test should clean up the trashed items after verifying them, similar to how other tests in this file use trash::os_limited::purge_all() to remove test artifacts. Consider adding cleanup code at the end of the test to prevent accumulation of test files in the trash.
| for name in names { | |
| assert!(deleted_names.iter().any(|item| item.name == name)); | |
| } | |
| for name in &names { | |
| assert!(deleted_names.iter().any(|item| item.name == *name)); | |
| } | |
| // Clean up trashed items created by this test to avoid accumulating artifacts. | |
| let targets: Vec<_> = trash::os_limited::list() | |
| .unwrap() | |
| .into_iter() | |
| .filter(|x| names.iter().any(|n| x.name == *n)) | |
| .collect(); | |
| trash::os_limited::purge_all(targets).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment is redundant because
time_deletedis already initialized to-1on line 496. This line can be removed to improve code clarity.