trash-5.2.0/.cargo_vcs_info.json 0000644 00000000136 00000000001 0012161 0 ustar {
"git": {
"sha1": "1a0fc5908a29c7e648a76ca706e2aa2a40eedda6"
},
"path_in_vcs": ""
} trash-5.2.0/CHANGELOG.md 0000644 0000000 0000000 00000214316 10461020230 0012571 0 ustar 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 5.2.0 (2024-10-26)
### New Features
- Short circuiting check for empty trash
`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.
### Commit Statistics
- 2 commits contributed to the release.
- 56 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Merge pull request #120 from joshuamegnauth54/feat-short-circuiting-is-empty ([`0120bbe`](https://github.com/Byron/trash-rs/commit/0120bbe66889e3659b9f09598f3567dd6c00d4b6))
- Short circuiting check for empty trash ([`6d59fa9`](https://github.com/Byron/trash-rs/commit/6d59fa939429d2eede8b7cf22b2e084bc3c546f4))
## 5.1.1 (2024-08-31)
This release accelerates removing large folders by removing an unnecessary recursive check.
### Commit Statistics
- 6 commits contributed to the release.
- 22 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#114](https://github.com/Byron/trash-rs/issues/114)
### Commit Details
view details
* **[#114](https://github.com/Byron/trash-rs/issues/114)**
- Merge pull request #114 from sungsphinx/fix-fedora-atomic ([`3d95173`](https://github.com/Byron/trash-rs/commit/3d95173d19bedf18d8b5b687567707bd99871e19))
* **Uncategorized**
- Release trash v5.1.1 ([`a2920fa`](https://github.com/Byron/trash-rs/commit/a2920fa50ad6dec4fc430c48b9837df2f17cd2f4))
- Adjust changelog prior to release ([`bc3e9c1`](https://github.com/Byron/trash-rs/commit/bc3e9c11426df512e3b056111863f8b410eaf043))
- Merge pull request #115 from NeumoNeumo/NeumoNeumo-patch-1 ([`df6f3b9`](https://github.com/Byron/trash-rs/commit/df6f3b99728a469f06027b2df486adc631ebc4ba))
- Accelerate by removing recursive renaming ([`8f8f5c0`](https://github.com/Byron/trash-rs/commit/8f8f5c06b2ce43d30c373311c643f184b7176d9f))
- Fix trashing files on Fedora Atomic variants ([`4d22ee4`](https://github.com/Byron/trash-rs/commit/4d22ee4852ba9b300489d332c210b920d01db8d9))
## 5.1.0 (2024-08-09)
### New Features
- check for operation abort
### Commit Statistics
- 3 commits contributed to the release.
- 52 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v5.1.0 ([`26e55ae`](https://github.com/Byron/trash-rs/commit/26e55aebf89ca3211787c787e8e2b22a412c4203))
- Merge pull request #113 from anatawa12/master ([`ca6d598`](https://github.com/Byron/trash-rs/commit/ca6d5980216eb9f2e4d709a08e0454502655e454))
- Check for operation abort ([`7919178`](https://github.com/Byron/trash-rs/commit/791917843a988396935ceff1eb5c982da6655d80))
## 5.0.0 (2024-06-18)
To support non-UTF8 encoding in paths, the `name` field changed from `String`
to `OsString` in the `TrashItem` struct. As it's a return value, one won't see
code break unless `name` is actually used.
### Bug Fixes
- Support non-Unicode paths
There are several spots where paths are assumed to be Unicode. However,
some (all?) operating systems support non-Unicode paths which causes
`trash-rs` to panic if encountered. I switched some of those code to use
`OsString`s instead of `String`s. Unfortunately, I had to add a new
dependency, `urlencoding`, in order to properly handle decoding non-UTF8
byte slices.
As of this commit, the test suite passes and code should be ready, but I
will try to remove the `url` crate and use `urlencoding` in its place
in the next commit.
### Other
- Use objc2-foundation
### Bug Fixes (BREAKING)
- Support non-UTF8 paths.
Note that this changes the type of returned paths to `OsString` from String,
hence the breaking change.
### Commit Statistics
- 11 commits contributed to the release over the course of 34 calendar days.
- 47 days passed between releases.
- 3 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v5.0.0 ([`a754f4a`](https://github.com/Byron/trash-rs/commit/a754f4a8c62737085c31982c1025544f3f36f5e8))
- Prepare changelog prior to release ([`02d1a8d`](https://github.com/Byron/trash-rs/commit/02d1a8d2494f26b79b907d516c0444df16d3e55a))
- Support non-UTF8 paths. ([`0971b8f`](https://github.com/Byron/trash-rs/commit/0971b8f7f0f1e20ee4356a40ae6b2ba41900c4b3))
- Update Windows code to account for API change ([`e4b7119`](https://github.com/Byron/trash-rs/commit/e4b7119fcc369c5594e9e2b5dad8f1a6616593f7))
- Simplify Linux/BSD only tests for non-UTF8 paths ([`559b57b`](https://github.com/Byron/trash-rs/commit/559b57bc1497d2a49ca4f463cc27f6c94697939c))
- Impl test for listing invalid UTF8 trash items ([`209db9d`](https://github.com/Byron/trash-rs/commit/209db9d76de1f233b05b10f5f3f008b5968b0232))
- Cleanup non-Unicode support for readability ([`2f31116`](https://github.com/Byron/trash-rs/commit/2f311164ff44077dc5450ebc0f14c29f70fe57d7))
- Remove `url` and replace with `urlencoding` ([`67fb256`](https://github.com/Byron/trash-rs/commit/67fb2568384b7ebd96acba54e40236d9f3e9eb07))
- Support non-Unicode paths ([`15a15f8`](https://github.com/Byron/trash-rs/commit/15a15f8ad10791318c6d9de95d4fbaefa345fb56))
- Merge pull request #107 from madsmtm/objc2 ([`46585ce`](https://github.com/Byron/trash-rs/commit/46585ceacc3799f74ce9793e6d0669eb4e48b3f8))
- Use objc2-foundation ([`58b99ef`](https://github.com/Byron/trash-rs/commit/58b99ef34a0dc6cce11fdc46c9fa18ffb013e33e))
## 4.1.1 (2024-05-01)
This release updates the `windows` dependency (on Windows) to v0.56.
### Commit Statistics
- 4 commits contributed to the release.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v4.1.1 ([`c14d904`](https://github.com/Byron/trash-rs/commit/c14d904864b4c030cc8ce9d8d394c719f40c5a5b))
- Update changelog prior to release. ([`47baa0e`](https://github.com/Byron/trash-rs/commit/47baa0ed3aaefa4167b1a972db54223ac710cb8d))
- Merge pull request #106 from YizhePKU/bump-windows ([`02f1e6c`](https://github.com/Byron/trash-rs/commit/02f1e6c5620f9a0bbeae68246cb2b180f946a1be))
- Bump windows crate to 0.56.0 ([`c0e0f7a`](https://github.com/Byron/trash-rs/commit/c0e0f7a6397bdb65de013ca8e2b58c6ea7ab73af))
## 4.1.0 (2024-03-19)
### New Features
- add `os_limited::trash_folders()` for use on many unixes.
### Commit Statistics
- 5 commits contributed to the release.
- 7 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v4.1.0 ([`35b549f`](https://github.com/Byron/trash-rs/commit/35b549f158428f0016a26c23249c76c6795ba9c3))
- Add `os_limited::trash_folders()` for use on many unixes. ([`eb659cb`](https://github.com/Byron/trash-rs/commit/eb659cb0ef5401c7ac1fb514c5d639c10464b730))
- Fix lint on Windows ([`daad5b7`](https://github.com/Byron/trash-rs/commit/daad5b7ad04192c7fac48b697e90ca40fb0cb94c))
- `trash_folders()` is invalid on Windows ([`32719fb`](https://github.com/Byron/trash-rs/commit/32719fbc82b572b77cd80d4f3645bb44ebca4640))
- List valid trash bin paths ([`3eba5c3`](https://github.com/Byron/trash-rs/commit/3eba5c36354eb246e74e31cab655d361313ff3e5))
## 4.0.0 (2024-03-12)
### Bug Fixes (BREAKING)
- Assure directory deletions on Windows don't put the entire contents into the trash.
Instead, like on other platforms, on Windows it will now put the folder into the trash instead.
Please note that this is not a breaking change in terms of API, but a *potentially* breaking change with older Windows versions. It's unknown if there are side-effects, as it's unknown why Windows had special behaviour previously.
### Commit Statistics
- 5 commits contributed to the release.
- 28 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v4.0.0 ([`b6acac9`](https://github.com/Byron/trash-rs/commit/b6acac9ef574649e381d10e3930348b4385ff551))
- Assure directory deletions on Windows don't put the entire contents into the trash. ([`146ea03`](https://github.com/Byron/trash-rs/commit/146ea03fe1c1c168b8a6fd135d9dc5c5c93f35d5))
- Fix lint by removing unused code ([`d03934f`](https://github.com/Byron/trash-rs/commit/d03934f668d1d405c2030685c318ba19b825c74e))
- Attempt to fix second argument issue ([`1435c3d`](https://github.com/Byron/trash-rs/commit/1435c3d566970f5366e990cdea512e1d0a6c738d))
- Make delete_all not recursive ([`03ae59d`](https://github.com/Byron/trash-rs/commit/03ae59d7473746e2d704aabab0d1065d3b9a6f58))
## 3.3.1 (2024-02-12)
### Bug Fixes
- Use `AtomicI32` instead of I64 for compatibility with `armel`
### Commit Statistics
- 2 commits contributed to the release.
- 2 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#99](https://github.com/Byron/trash-rs/issues/99)
### Commit Details
view details
* **[#99](https://github.com/Byron/trash-rs/issues/99)**
- Use `AtomicI32` instead of I64 for compatibility with `armel` ([`98049f1`](https://github.com/Byron/trash-rs/commit/98049f1316e3902f2c9d5cd51f8de14b86ec5828))
* **Uncategorized**
- Release trash v3.3.1 ([`b6e2d6c`](https://github.com/Byron/trash-rs/commit/b6e2d6c57f499a1851e8b2e4a724b1e0ef5ae54d))
## 3.3.0 (2024-02-10)
### New Features
- improved error granularity
Inform about operating-system specific errors more clearly, thus avoid degenerating error information.
### Bug Fixes
- Use `AtomicI32` in tests for compatibility with `armel` platform
### Commit Statistics
- 6 commits contributed to the release over the course of 5 calendar days.
- 25 days passed between releases.
- 2 commits were understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#99](https://github.com/Byron/trash-rs/issues/99)
### Commit Details
view details
* **[#99](https://github.com/Byron/trash-rs/issues/99)**
- Use `AtomicI32` in tests for compatibility with `armel` platform ([`920ff0c`](https://github.com/Byron/trash-rs/commit/920ff0c69f6d0309e73f86aaa437aec9508cc873))
* **Uncategorized**
- Release trash v3.3.0 ([`d0d8f26`](https://github.com/Byron/trash-rs/commit/d0d8f26030e0936aa57aa1d0d4e1a34f6a91f5b9))
- Improved error granularity ([`452be83`](https://github.com/Byron/trash-rs/commit/452be8303c797f44409b487c0cf1e6ffb2899110))
- Removed tracing. ([`2b1c9fa`](https://github.com/Byron/trash-rs/commit/2b1c9fa2a9743c1d5477bf5512ba0f260cfdacb5))
- Bug fix for macOS. ([`b238938`](https://github.com/Byron/trash-rs/commit/b238938d7d6387d7340f9c6a30025c9255973180))
- Enhanced error reporting. ([`671cef9`](https://github.com/Byron/trash-rs/commit/671cef91f4e3c216f84683e07c82c5849d641b3b))
## 3.2.1 (2024-01-15)
### Bug Fixes
- find best-possible trash dir, e.g. use `/run/foo/.trash` instead of`/run/.trash` when deleting `/run/foo/bar`.
### Commit Statistics
- 10 commits contributed to the release.
- 5 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.2.1 ([`d7abb5b`](https://github.com/Byron/trash-rs/commit/d7abb5bb735827b88479fc4879dcfcdcae6e08df))
- Find best-possible trash dir, e.g. use `/run/foo/.trash` instead of`/run/.trash` when deleting `/run/foo/bar`. ([`bb868d6`](https://github.com/Byron/trash-rs/commit/bb868d6812988b56082c2faea083617402e1a259))
- Refactor ([`8cb3f75`](https://github.com/Byron/trash-rs/commit/8cb3f7519b1294fe8b2e03c0f51fd129bb9f4cf4))
- Cargo fmt ([`0b42fc0`](https://github.com/Byron/trash-rs/commit/0b42fc06b44e076aa7aebaee6f8730bc762ee5ed))
- Use unstable sort ([`18dadef`](https://github.com/Byron/trash-rs/commit/18dadef0dd39bf3e57450fbf4a7098688fb81df0))
- Fixing method os ([`8ba855e`](https://github.com/Byron/trash-rs/commit/8ba855e4bf9982e8b4be993d8df59739b88d72c6))
- Sort mount points first ([`b2e4cf2`](https://github.com/Byron/trash-rs/commit/b2e4cf202e108bb419d7a7e5959b45408dac836c))
- Refactor ([`da8ce63`](https://github.com/Byron/trash-rs/commit/da8ce63afd331b4e41455be0587a2736c42815bd))
- Fix clippy error ([`8f74b17`](https://github.com/Byron/trash-rs/commit/8f74b1789a2257ba5a7acda560f1811df8f5f1ea))
- Fixing sometimes choosing incorrect mount point if substring of each other ([`1e9df03`](https://github.com/Byron/trash-rs/commit/1e9df0347cd1298844222a43a6424400e7dc787b))
## 3.2.0 (2024-01-10)
### New Features
- provide `os_limited::metadata()`.
Metadata is currently limited to the amount of things, like bytes or entries,
in the metadata item, but there is potential for adding more later.
### Other
- update ci job to use cargo-cross
### Commit Statistics
- 14 commits contributed to the release.
- 2 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.2.0 ([`03aa7ac`](https://github.com/Byron/trash-rs/commit/03aa7ac1fc279d1cb598c451d8ef342d13232489))
- Provide `os_limited::metadata()`. ([`aa8e504`](https://github.com/Byron/trash-rs/commit/aa8e5043e285d31644e697aa264f8a11e5dfa2e8))
- Refactor ([`8dad3df`](https://github.com/Byron/trash-rs/commit/8dad3dfc45657962a57a932c40bc37ea1ebe0d7f))
- Address review comments ([`63639c3`](https://github.com/Byron/trash-rs/commit/63639c3337cc282a1aaa69ef5afd00f8516e3dcd))
- Stub for get_mount_points on unsupported targets ([`fd89ea5`](https://github.com/Byron/trash-rs/commit/fd89ea5d780fa111d12fbe6644dc4153a78565c5))
- Windows implementation ([`1a1f75e`](https://github.com/Byron/trash-rs/commit/1a1f75e59b4c18abdf6bc8790a4e54b53dff50df))
- Add metadata function, implement for freedesktop ([`3bea3e2`](https://github.com/Byron/trash-rs/commit/3bea3e2f11d5def136455e7bc2377cb05b80147e))
- Merge pull request #92 from TD-Sky/unknown-to-fs-error ([`916d769`](https://github.com/Byron/trash-rs/commit/916d7698ebceb0529fa3c43f6baddbd4c39d55f2))
- Accepting generic type instead of `&Path` ([`17411be`](https://github.com/Byron/trash-rs/commit/17411be41b96f4a81df8a9cc6fa558d0d250c749))
- Be consistent with the style of the project ([`7ee2617`](https://github.com/Byron/trash-rs/commit/7ee26179e59c4920b83fffed20a049e9171e4878))
- Keep error converter function and rename it `fs_error` ([`a08118c`](https://github.com/Byron/trash-rs/commit/a08118cf2a924a3224b05d76dd5b012036ef5e05))
- More precise file system error ([`c51aa78`](https://github.com/Byron/trash-rs/commit/c51aa7820c70e6d5fc4d408f5c01cd4c8701c59d))
- Merge pull request #90 from fujiapple852/build-add-cargo-cross-ci ([`695af32`](https://github.com/Byron/trash-rs/commit/695af324e6ddaee00ea0ee5e44c7d815fd1158ec))
- Update ci job to use cargo-cross ([`be43b09`](https://github.com/Byron/trash-rs/commit/be43b098c6c4db66f19c90471cd6ff0c066832ef))
## 3.1.2 (2023-10-18)
This release fixes compile errors on DragonFly, a fork of FreeBSD.
### Commit Statistics
- 4 commits contributed to the release.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.1.2 ([`609f6b3`](https://github.com/Byron/trash-rs/commit/609f6b39f6a743e1cbd9226c873b0463730e10ed))
- Prepare changelog ([`c81d4dd`](https://github.com/Byron/trash-rs/commit/c81d4ddccc9bd30b5baecb9c69f01437b467a703))
- Merge pull request #89 from jbeich/dragonfly ([`ad26100`](https://github.com/Byron/trash-rs/commit/ad261004b4fe350bf7963cc4354e4b5808c61156))
- Add DragonFly support via FreeBSD codepath ([`ed1984b`](https://github.com/Byron/trash-rs/commit/ed1984b923a7cdd7dbf03484d02b5da07e27779c))
## 3.1.1 (2023-10-18)
### Bug Fixes
- compilation on FreeBSD should work now. #(86)
### Commit Statistics
- 5 commits contributed to the release over the course of 9 calendar days.
- 9 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.1.1 ([`aa6fd20`](https://github.com/Byron/trash-rs/commit/aa6fd20ec75585f7cda5e8745a6cafd2c5b26e91))
- Compilation on FreeBSD should work now. #(86) ([`1a5bc2d`](https://github.com/Byron/trash-rs/commit/1a5bc2de178ca76fe06631a09305e4f014764084))
- Update freedesktop.rs ([`aa7b7fd`](https://github.com/Byron/trash-rs/commit/aa7b7fd66573631cf17b031b90e5e0139f0fdab6))
- Restore statfs for FreeBSD & OpenBSD ([`1562113`](https://github.com/Byron/trash-rs/commit/1562113e12f9020a9c3f866e5adf5e913f4040e6))
- Update version in README so it matches the latest published one ([`50e8030`](https://github.com/Byron/trash-rs/commit/50e80304845cbae953b4ecf370c715d728ea9958))
## 3.1.0 (2023-10-08)
### New Features
- compatibility with OpenBSD and NetBSD
- allow passing in items' ownership or reference
### Other
- describe how to retry restoring when encountering `RestoreCollision` error
### Commit Statistics
- 10 commits contributed to the release.
- 88 days passed between releases.
- 3 commits were understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#84](https://github.com/Byron/trash-rs/issues/84)
### Commit Details
view details
* **[#84](https://github.com/Byron/trash-rs/issues/84)**
- Compatibility with OpenBSD and NetBSD ([`24e0cb6`](https://github.com/Byron/trash-rs/commit/24e0cb6f9fe15a0db1609e04cda6446e3335f89b))
* **Uncategorized**
- Release trash v3.1.0 ([`be17cd2`](https://github.com/Byron/trash-rs/commit/be17cd20bb32ab00ceb72cd9afc3ddaed01cacdb))
- Bump minor version to indicate a feature change ([`ddb9917`](https://github.com/Byron/trash-rs/commit/ddb99171715727a3339d4a9e2f07a517037b01db))
- Merge pull request #81 from TD-Sky/re-restore ([`c87a946`](https://github.com/Byron/trash-rs/commit/c87a9467235e6208e2268d392ac61f332b4d1d09))
- Test edition bump ([`b77bd6d`](https://github.com/Byron/trash-rs/commit/b77bd6d32f8d44f59b9fe53806248d0b0860aa18))
- Bump version ([`75cc270`](https://github.com/Byron/trash-rs/commit/75cc27093d01628fb79acb1432c8ccdd66d86b2f))
- Update dependencies ([`7d1e2bb`](https://github.com/Byron/trash-rs/commit/7d1e2bb0a51d88033428aad62bf87e400c2a334d))
- One step closer ([`aee3dce`](https://github.com/Byron/trash-rs/commit/aee3dceac5575e4a2a23633ec5f3da5da79d9e89))
- Allow passing in items' ownership or reference ([`0789b23`](https://github.com/Byron/trash-rs/commit/0789b23c6c8e21bc1493455beaca75d46e0aa575))
- Describe how to retry restoring when encountering `RestoreCollision` error ([`554c273`](https://github.com/Byron/trash-rs/commit/554c2735c8dd924fd7cebe863b529d91bb0cac0d))
## 3.0.6 (2023-07-12)
### Bug Fixes
- don't recurse into symlink when trashing a directory on windows.
### Commit Statistics
- 4 commits contributed to the release.
- 5 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.0.6 ([`450edc1`](https://github.com/Byron/trash-rs/commit/450edc1a0d372ae450daf3aec33aabedd3efde3d))
- Merge branch 'fix-symlink-traversal' ([`43d44cb`](https://github.com/Byron/trash-rs/commit/43d44cbe0979c92cbc117723387d762ecd9d3191))
- Don't recurse into symlink when trashing a directory on windows. ([`3f5e842`](https://github.com/Byron/trash-rs/commit/3f5e8427cbf299322d66b358ec3fa61ca4a5d66c))
- Inform about reason for yanking v3.0.5 ([`112e99e`](https://github.com/Byron/trash-rs/commit/112e99ecfd485c5115323b185efc5979eae26edc))
## 3.0.5 (2023-07-06)
YANKED: It was discovered that symlinks aren't handled correctly, which can lead to removals of unrelated directory trees.
### Bug Fixes
- On **windows**, `delete()` will now delete recursively like on the other platforms.
Note that the current implementation may consume a lot of memory as it will traverse the
entire directory structure once while storing each path for later trashing.
### Commit Statistics
- 9 commits contributed to the release over the course of 1 calendar day.
- 4 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.0.5 ([`4655a07`](https://github.com/Byron/trash-rs/commit/4655a0723ab4209872d4037be89d2b0876a70731))
- Upgrade serial-test crate ([`0354d36`](https://github.com/Byron/trash-rs/commit/0354d36b7f870317cf57711624fd31054ffc946e))
- On **windows**, `delete()` will now delete recursively like on the other platforms. ([`c1feece`](https://github.com/Byron/trash-rs/commit/c1feece952dcd70163ed06ac2af79fdbb3d692bc))
- Refactor ([`41edcdf`](https://github.com/Byron/trash-rs/commit/41edcdfc8bdeb410b45ae636da25e3c7275a8a8c))
- Removed self as parameter only used in recurssion. ([`a7619c1`](https://github.com/Byron/trash-rs/commit/a7619c13215daaf88316f7e1876cf59c96491cf4))
- Reorganized code for cross-platform compatibility. ([`1c09e48`](https://github.com/Byron/trash-rs/commit/1c09e48c7977704b1a8d67078c84ed30b17c983a))
- Use recursive deletion on Windows by default. ([`46e0697`](https://github.com/Byron/trash-rs/commit/46e0697c649f9e8184654e47f18f6b2930b6bd67))
- Removed Windows only restriction for recursive deletion test. ([`d363dd8`](https://github.com/Byron/trash-rs/commit/d363dd840a0d35348b427ff6d1f6def568e008ed))
- Merge branch 'Byron:master' into bug/windows_nonempty_folder ([`0f4b2c8`](https://github.com/Byron/trash-rs/commit/0f4b2c81a209f70592b33675144c1d7922433741))
## 3.0.4 (2023-07-01)
### Bug Fixes
- Don't use 'oldtime' feature of `chrono` by controlling exactly which features are enabled.
That particular feature has [a rustsec advisory](https://rustsec.org/advisories/RUSTSEC-2020-0071) up
against it.
### Commit Statistics
- 3 commits contributed to the release.
- 19 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#75](https://github.com/Byron/trash-rs/issues/75)
### Commit Details
view details
* **[#75](https://github.com/Byron/trash-rs/issues/75)**
- Don't use 'oldtime' feature of `chrono` by controlling exactly which features are enabled. ([`55b0d5c`](https://github.com/Byron/trash-rs/commit/55b0d5c86e2608552836ec0bf3e9aa0ce8c303b8))
* **Uncategorized**
- Release trash v3.0.4 ([`a2343c2`](https://github.com/Byron/trash-rs/commit/a2343c2692aa8d6b5fc8684a654349a14094486b))
- Don't use `oldtime` feature of chrono ([`fad81a4`](https://github.com/Byron/trash-rs/commit/fad81a4992fe053e30113f9ab0c7001d12b1ec17))
## 3.0.3 (2023-06-11)
### Bug Fixes
- disallow empty paths from being deleted.
Previously passing "" for deletion wuold delete the current working directory
as it would canonicalize any input path without validating the path is non-empty.
### Commit Statistics
- 3 commits contributed to the release over the course of 11 calendar days.
- 25 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#73](https://github.com/Byron/trash-rs/issues/73)
### Commit Details
view details
* **[#73](https://github.com/Byron/trash-rs/issues/73)**
- Disallow empty paths from being deleted. ([`aa8cd7b`](https://github.com/Byron/trash-rs/commit/aa8cd7b05f8f0641d7fd73328619c2c45c7e050c))
* **Uncategorized**
- Release trash v3.0.3 ([`841bc13`](https://github.com/Byron/trash-rs/commit/841bc1388959ab3be4f05ad1a90b03aa6bcaea67))
- Fix issue #70.Added recursive removal on Windows. ([`05e0cf4`](https://github.com/Byron/trash-rs/commit/05e0cf442354b3b2b9ecfb8ed2b165b8547bc794))
## 3.0.2 (2023-05-17)
### Bug Fixes
- broken symlinks won't cause failure anymore on freedesktop platforms.
### Commit Statistics
- 4 commits contributed to the release.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.0.2 ([`e20fe6a`](https://github.com/Byron/trash-rs/commit/e20fe6ae94aa73d07ff31d911ad9ecf98b17f3a8))
- Broken symlinks won't cause failure anymore on freedesktop platforms. ([`75daea6`](https://github.com/Byron/trash-rs/commit/75daea606cbdbc4d15a514bb674591d986e57490))
- Make `virtually_exists` private ([`454a77e`](https://github.com/Byron/trash-rs/commit/454a77e667b00a0aeb492dab9a81e69e77178802))
- Operate broken symbolic links is safe now ([`9198013`](https://github.com/Byron/trash-rs/commit/919801376bc44fa3c4948349690c7e912be2dd3a))
## 3.0.1 (2023-01-30)
### Chore
- bump `windows` crate to 0.44
Merge branch 'bump-windows-0.44'
### Commit Statistics
- 5 commits contributed to the release over the course of 61 calendar days.
- 64 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Thanks Clippy
[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
### Commit Details
view details
* **Uncategorized**
- Release trash v3.0.1 ([`eef463a`](https://github.com/Byron/trash-rs/commit/eef463aca73d5c623dd7b52bcb8b01b3b3d76b15))
- Bump `windows` crate to 0.44 ([`865a7c6`](https://github.com/Byron/trash-rs/commit/865a7c6d688cc6dd00dc8b16cd0e4a4fd60d953c))
- Thanks clippy ([`37dedb3`](https://github.com/Byron/trash-rs/commit/37dedb35ed71e4c43af3af7d39ae5d722c8b5a94))
- Update `windows` crate to `0.44` ([`1a347fc`](https://github.com/Byron/trash-rs/commit/1a347fcce57627dd71979ca8399dedba149f9569))
- Add `Error::FileSystem` ([`575b8ed`](https://github.com/Byron/trash-rs/commit/575b8ed4c78b76e9ecdf4fe877b6e32cd74cf166))
## 3.0.0 (2022-11-27)
### Chore (BREAKING)
- Upgrade from `windows` v0.37 to v0.43.
### Commit Statistics
- 5 commits contributed to the release.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v3.0.0 ([`1fb5ad6`](https://github.com/Byron/trash-rs/commit/1fb5ad628868f1480510efe10bdc021ce65b4f32))
- Upgrade from `windows` v0.37 to v0.43. ([`a024b44`](https://github.com/Byron/trash-rs/commit/a024b44b6e1cd4a357ffabda8f31e82dcc7e78cb))
- Fix Clippy failures on Linux ([`538dea0`](https://github.com/Byron/trash-rs/commit/538dea0e77af2ed70c6f8b17c86b956b8caa6459))
- Upgrade windows crate from v0.37 to v0.43 ([`48cdc67`](https://github.com/Byron/trash-rs/commit/48cdc67d09e20f8d07438e45d3ceefd23da6af9a))
- Derive Clone for TrashItem ([`fcf6bb5`](https://github.com/Byron/trash-rs/commit/fcf6bb5eded49de4fedb40513c949f11c6da0b12))
## 2.1.5 (2022-07-05)
### Bug Fixes
- Make chrono a default-enabled optional feature.
This allows to turn chrono support off without actually affecting the
ability to trash and restore items.
`chrono` still has issues to dubious local-time support which relies
on a c-library function that can cause undefined behaviour as it
accesses an environment variable in a non-threadsafe fashion.
### Commit Statistics
- 5 commits contributed to the release.
- 40 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#39](https://github.com/Byron/trash-rs/issues/39)
### Commit Details
view details
* **[#39](https://github.com/Byron/trash-rs/issues/39)**
- Make chrono a default-enabled optional feature. ([`67244ba`](https://github.com/Byron/trash-rs/commit/67244ba2e4c71135b0ab36331dc465615e23211a))
* **Uncategorized**
- Release trash v2.1.5 ([`266d780`](https://github.com/Byron/trash-rs/commit/266d7808d2309f0911ebc6c8a0189511c4e77835))
- Improve CI stage names; fix feature configuration on windows ([`5591fda`](https://github.com/Byron/trash-rs/commit/5591fdab131de1f6fa5a04bef44d7b394d3f7f72))
- Silence clippy ([`d13be48`](https://github.com/Byron/trash-rs/commit/d13be48c59a1a0df3e37aa676cda06cc1f48ece9))
- Add rust-cache for faster builds ([`676a43f`](https://github.com/Byron/trash-rs/commit/676a43f7ec7c116a7b40dcf4236bf2156a88fd04))
## 2.1.4 (2022-05-25)
### Fixes
- upgrade the `windows` crate to v0.37 to resolve [a build issue](https://github.com/Byron/trash-rs/issues/39) and lay the foundation
for more regular updates of the windows support.
### Commit Statistics
- 3 commits contributed to the release.
- 8 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 2 unique issues were worked on: [#39](https://github.com/Byron/trash-rs/issues/39), [#51](https://github.com/Byron/trash-rs/issues/51)
### Commit Details
view details
* **[#39](https://github.com/Byron/trash-rs/issues/39)**
- Prepare changelog ([`7816e07`](https://github.com/Byron/trash-rs/commit/7816e07bab38a79aa6f5d705a4fb40f330ac155b))
* **[#51](https://github.com/Byron/trash-rs/issues/51)**
- Upgrade windows crate ([`d18f9d4`](https://github.com/Byron/trash-rs/commit/d18f9d435d2f76fb982f4bfcc98d5ccfe57c092c))
* **Uncategorized**
- Release trash v2.1.4 ([`17d162f`](https://github.com/Byron/trash-rs/commit/17d162fcf7a53d3d82961a448d4b70b4eb596825))
## 2.1.3 (2022-05-17)
### Fixes
- include `windows` crate only on windows for reduced CI build times from ~9s to ~4s.
### Commit Statistics
- 5 commits contributed to the release.
- 3 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#5050505050](https://github.com/Byron/trash-rs/issues/5050505050)
### Commit Details
view details
* **[#5050505050](https://github.com/Byron/trash-rs/issues/5050505050)**
- Update changelog ([`8e64f34`](https://github.com/Byron/trash-rs/commit/8e64f34bd6f1b823353fae61d60f765615be0024))
* **Uncategorized**
- Release trash v2.1.3 ([`f98bc45`](https://github.com/Byron/trash-rs/commit/f98bc45199cbb24525d2b41c748b9547f3c3ac44))
- Merge pull request #50 from rgwood/windows-dep ([`883c5a4`](https://github.com/Byron/trash-rs/commit/883c5a48c8ad07bef4f7e1822a31761211cf304d))
- Add names to CI steps ([`ef7003a`](https://github.com/Byron/trash-rs/commit/ef7003a4f83910f318b05a3f51960a33fd444915))
- Only use `windows` crate on Windows ([`e088525`](https://github.com/Byron/trash-rs/commit/e088525047a14a531d414fe9cd098e08fe2ff79f))
## 2.1.2 (2022-05-13)
### Bug Fixes
- avoid inconsistency when using relative paths in trashed file info.
We use absolute paths now without trying to generate a relative path
based on some top directory as the latter seems to be causing
inconsistencies on some linux distros, as the restore path ends
up being incorrect.
Rather go with the absolute truth and don't fiddle with path
transformations at all to make it work everywhere.
### Commit Statistics
- 2 commits contributed to the release.
- 2 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#39](https://github.com/Byron/trash-rs/issues/39)
### Commit Details
view details
* **[#39](https://github.com/Byron/trash-rs/issues/39)**
- Avoid inconsistency when using relative paths in trashed file info. ([`367cf5f`](https://github.com/Byron/trash-rs/commit/367cf5f2616f1f49b115189b3bede3bb99f8324d))
* **Uncategorized**
- Release trash v2.1.2 ([`e0746f0`](https://github.com/Byron/trash-rs/commit/e0746f0df91623231d13531ec33632f03f0588ac))
## 2.1.1 (2022-05-10)
### Bug Fixes
- Properly reconstruct paths when restoring files on freedesktop if those were relative.
Previously it would be unable to reconstruct original paths if the trash
directory was on a mount point due to a 'split brain' of sorts.
When trashing files it would create original path information based
on them being relative to a mount point, but when restoring them
it would reconstruct them to be relative to the trash top level
directory.
Now the reconstruction happens against to mount point itself which makes
restoration match.
### Commit Statistics
- 7 commits contributed to the release over the course of 2 calendar days.
- 3 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#47](https://github.com/Byron/trash-rs/issues/47)
### Commit Details
view details
* **[#47](https://github.com/Byron/trash-rs/issues/47)**
- Properly reconstruct paths when restoring files on freedesktop if those were relative ([`dcda6df`](https://github.com/Byron/trash-rs/commit/dcda6df8cefa06bf08e7eca7db2c34b050c2d913))
- Somewhat hard-code special case for fedora ([`90f0f9b`](https://github.com/Byron/trash-rs/commit/90f0f9b035678efe51a20d4a47fd09158b8ef455))
- Proper cleanup after potential assertion failure ([`1f3a600`](https://github.com/Byron/trash-rs/commit/1f3a6005eabd4629fe0743030a612a29fcb7d80c))
- Remove unused trait ([`ac913d8`](https://github.com/Byron/trash-rs/commit/ac913d83ed9344d8ed8e18957b2e99136e0b29c1))
* **Uncategorized**
- Release trash v2.1.1 ([`50ab31a`](https://github.com/Byron/trash-rs/commit/50ab31afa9f641a16a1ab50bf1ea8f8bacb0330f))
- Update changelog ([`98d32c8`](https://github.com/Byron/trash-rs/commit/98d32c88e85b2b40ea17d372c427ef168ad80b30))
- More robust removal of test files in failure case on os specific tests ([`3f6502d`](https://github.com/Byron/trash-rs/commit/3f6502db02e09e36c2fbce2fea054a9a2b9229de))
## 2.1.0 (2022-05-06)
### Fixes
- Leading directories are now created on linux to avoid errors when trashing nested directories.
### Commit Statistics
- 8 commits contributed to the release.
- 103 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 2 unique issues were worked on: [#45](https://github.com/Byron/trash-rs/issues/45), [#47](https://github.com/Byron/trash-rs/issues/47)
### Commit Details
view details
* **[#45](https://github.com/Byron/trash-rs/issues/45)**
- Reproduce issue with lack of leading directories and fix it ([`d5b6faa`](https://github.com/Byron/trash-rs/commit/d5b6faa81d59ccd6185261399bc7449432b9deb6))
* **[#47](https://github.com/Byron/trash-rs/issues/47)**
- Try to reproduce ([`8eba501`](https://github.com/Byron/trash-rs/commit/8eba50155e006cf923d8bb77fea88cde6395512e))
* **Uncategorized**
- Release trash v2.1.0 ([`b3a4547`](https://github.com/Byron/trash-rs/commit/b3a45471ce5fcd489a096145e06ac663ed854747))
- Prepare upcoming release ([`e3bbb6b`](https://github.com/Byron/trash-rs/commit/e3bbb6be1072675c331176e8d0585cc67910d17b))
- Merge branch 'refactor-tests' ([`0e90cac`](https://github.com/Byron/trash-rs/commit/0e90cace515344c68eead8e59180487561849289))
- Assure tests don't race ([`d9778ba`](https://github.com/Byron/trash-rs/commit/d9778ba1912c5764cbfaa9c46b2bba5c3d1899eb))
- Thanks clippy ([`220a216`](https://github.com/Byron/trash-rs/commit/220a2164e86bf7f0e1e636d24595b6ce4182de14))
- Move all intergration tests into corresponding location ([`e5dc62e`](https://github.com/Byron/trash-rs/commit/e5dc62ee2b363a11e57e4aad2c1d128d2f8961e2))
## 2.0.4 (2022-01-23)
We detected the possibility of UB in the Linux and FreeBSD versions of `get_mount_points()` and reduced the likelihood
of it happening in a multi-threaded environment by synchronizing access. You can read more about the state of
a more permanent fix [in the tracking issue](https://github.com/Byron/trash-rs/issues/42).
All previous 2.0.* releases which contained this function were yanked from crates-io.
### Fixes
* Make internal `get_mount_points()` thread-safe to reduce chance of UB greatly.
This may reduce performance of crates that are using trash from multiple threads somewhat, as a part of the operation
is now synchronized.
* Fix build on FreeBSD, handle UB similarly to the above.
### Commit Statistics
- 10 commits contributed to the release over the course of 30 calendar days.
- 30 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release trash v2.0.4 ([`c7edcb1`](https://github.com/Byron/trash-rs/commit/c7edcb175dd125bda5b15e726fc7b36eae3c89a4))
- Prepare changelog for next release ([`b65f574`](https://github.com/Byron/trash-rs/commit/b65f574d5aeb8ea3a918e8288c8d13dd082b8f0a))
- Add Mutex to linux version of get_mount_points(); document UB chance in lib.rs ([`c5c9c5e`](https://github.com/Byron/trash-rs/commit/c5c9c5e40d345736df7d078bf8e6991acc701e83))
- Use Mutex to prevent concurrent access to getmntinfo ([`5c8e0ce`](https://github.com/Byron/trash-rs/commit/5c8e0ce1c700c68fc63c612cc0ea5b3191f6b0d1))
- Merge pull request #43 from wezm/num-threads-freebsd ([`8f10c85`](https://github.com/Byron/trash-rs/commit/8f10c852bd9ec2e69353a0dd5397fab1c4ba089f))
- Fix build on FreeBSD after refactor ([`f3d31e5`](https://github.com/Byron/trash-rs/commit/f3d31e54dd93c22605e8178958a1caa503be19f4))
- Use `num_threads()` to avoid UB in FreeBSD version of get_mount_points() ([`3c153ae`](https://github.com/Byron/trash-rs/commit/3c153ae2f1ed92d8a240a742e90fcb0e483284b8))
- Refactor ([`92ab7b9`](https://github.com/Byron/trash-rs/commit/92ab7b91adcde3305cc3e319fb0b59feff8f81cc))
- Add BSD compatible implementation of get_mount_points ([`82d2132`](https://github.com/Byron/trash-rs/commit/82d2132f8e1323272f5d8e1f54112589f75c3202))
- Run `cargo-diet` for a more minimal crates package ([`561f21d`](https://github.com/Byron/trash-rs/commit/561f21d9de2a56cb0f0c87002d2ead3dc8ca6ab2))
## 2.0.3 (2021-12-23)
### Bug Fixes
- let dependency specification in Cargo.toml match cfg directives in code
This fixes [issue 40](https://github.com/Byron/trash-rs/issues/40).
### Commit Statistics
- 6 commits contributed to the release.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 2 unique issues were worked on: [#37](https://github.com/Byron/trash-rs/issues/37), [#40](https://github.com/Byron/trash-rs/issues/40)
### Commit Details
view details
* **[#37](https://github.com/Byron/trash-rs/issues/37)**
- Fix some clippy warnings ([`3c566ef`](https://github.com/Byron/trash-rs/commit/3c566ef417350b75e02ea80be51165815014ec74))
* **[#40](https://github.com/Byron/trash-rs/issues/40)**
- Let dependency specification in Cargo.toml match cfg directives in code ([`cb5b617`](https://github.com/Byron/trash-rs/commit/cb5b6176aa296853f7a6e3cfa177e1235acaa903))
* **Uncategorized**
- Release trash v2.0.3 ([`6864e34`](https://github.com/Byron/trash-rs/commit/6864e340890f247f675982744396bae8ea856565))
- Disable lint for platforms where it matters ([`b4add86`](https://github.com/Byron/trash-rs/commit/b4add8643cc0659b4318f3113a197794cb0032b0))
- Update changelog with `cargo changelog` ([`932cea4`](https://github.com/Byron/trash-rs/commit/932cea48c6ceba2adf0b824c3236b330e232de12))
- Add Rust CI status badge ([`b94fce2`](https://github.com/Byron/trash-rs/commit/b94fce2bf74dd5c1ee66735eca32d6ace5db83ea))
## v2.0.2 (2021-08-18)
### Changed
- Fix failing to delete files on some freedesktop (eg Linux) systems when the home was not mounted at the root.
- The `list` function now returns an empty list if there is no trash directory (it used to return an error).
- Fix for test failing on Linux environments that don't have a desktop environment (more specifically don't have a tool like `gio`)
### Commit Statistics
- 10 commits contributed to the release over the course of 104 calendar days.
- 108 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 3 unique issues were worked on: [#34](https://github.com/Byron/trash-rs/issues/34), [#35](https://github.com/Byron/trash-rs/issues/35), [#36](https://github.com/Byron/trash-rs/issues/36)
### Commit Details
view details
* **[#34](https://github.com/Byron/trash-rs/issues/34)**
- Fix for failing to delete files on Freedesktop systems (eg Linux) ([`bd8679c`](https://github.com/Byron/trash-rs/commit/bd8679c39b163e87c33bec7a669cebdc9ff37358))
* **[#35](https://github.com/Byron/trash-rs/issues/35)**
- Fix for test failing on some Linux environments ([`9da7b59`](https://github.com/Byron/trash-rs/commit/9da7b590a23940693ad2809ca28c7ec904a574a6))
* **[#36](https://github.com/Byron/trash-rs/issues/36)**
- Avoid error from the list function ([`cb59c7e`](https://github.com/Byron/trash-rs/commit/cb59c7e09f6409881c24131bf25cb89930203655))
* **Uncategorized**
- Update version ([`600b59c`](https://github.com/Byron/trash-rs/commit/600b59c3422d5f6f51aca27b867a64650f06c865))
- Update windows-rs ([`2b64f38`](https://github.com/Byron/trash-rs/commit/2b64f3832781b2715688c236194392ec31b2c5d3))
- Some minor improvements ([`0e281bc`](https://github.com/Byron/trash-rs/commit/0e281bcbfe0bb50d8b68782cdd1da7d7e74355f7))
- Merge pull request #29 from ArturKovacs/update-win-rs ([`2a1eaf8`](https://github.com/Byron/trash-rs/commit/2a1eaf8630b2c49b06e28d323d85e95dd0dd514a))
- Revert the build script ([`1b4a501`](https://github.com/Byron/trash-rs/commit/1b4a501685fa02e80579fa825156ec1077a39519))
- Ran cargo fmt ([`42884ae`](https://github.com/Byron/trash-rs/commit/42884aec20b1ad1b59213b465b34b600a8bf4cff))
- Update windows-rs and fix for cross compilation ([`681d7b4`](https://github.com/Byron/trash-rs/commit/681d7b49140c0fd1db33628ee66bf432a5818eac))
## v2.0.1 (2021-05-02)
### Changed
- Fix not being able to trash any item on some systems.
### Commit Statistics
- 4 commits contributed to the release.
- 11 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Update version number ([`6f11f8d`](https://github.com/Byron/trash-rs/commit/6f11f8dd58190afd00b15211584c15919477ad07))
- Merge pull request #26 from ArturKovacs/fix-25 ([`13a36ce`](https://github.com/Byron/trash-rs/commit/13a36cec736c8127676f90f45f0c3941590aca1d))
- Update changelog ([`812b574`](https://github.com/Byron/trash-rs/commit/812b574f08c73b3b26cd3c1b4b761e209f9544df))
- Fix for error when trashing an item ([`a876d0f`](https://github.com/Byron/trash-rs/commit/a876d0f92e48cae89ac4815187b0bdff7634148d))
## v2.0.0 (2021-04-20)
### Changed
- The "Linux" implementation was replaced by a custom Freedesktop implementation.
### Added
- `list`, `purge_all`, and `restore_all` to Windows and Freedesktop
### Commit Statistics
- 32 commits contributed to the release over the course of 4 calendar days.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Merge pull request #11 from ArturKovacs/v2-dev ([`3dcac24`](https://github.com/Byron/trash-rs/commit/3dcac248a029a7f78d41fcdf1645f7ad6dc5bc4d))
- Merge branch 'v2-dev' of https://github.com/ArturKovacs/trash-rs into v2-dev ([`e9047a3`](https://github.com/Byron/trash-rs/commit/e9047a364ba531c720d356867649d13dcac1f918))
- Update the version number ([`28307a6`](https://github.com/Byron/trash-rs/commit/28307a662c99b150268d7b20d946ee9bd51baa75))
- Add test for NsFileManager delete method ([`8aea6ef`](https://github.com/Byron/trash-rs/commit/8aea6ef92cde4daa6336424c91192d80ca62bde6))
- Run cargo fmt ([`3ce2160`](https://github.com/Byron/trash-rs/commit/3ce2160a87de0b88901048063cc5d7b5aa8455f2))
- Fix clippy error ([`2158550`](https://github.com/Byron/trash-rs/commit/21585507786f8ab0426afbe2dab7dd738b6c8c84))
- More tweaks ([`ee2527a`](https://github.com/Byron/trash-rs/commit/ee2527a78134e408f73347b4f6bfaf43d2f9fb29))
- Minor tweaks ([`1c43fe7`](https://github.com/Byron/trash-rs/commit/1c43fe7e7de12e1ecd551633b62c6105cfa4019d))
- Update readme, add changelog ([`4c1ece3`](https://github.com/Byron/trash-rs/commit/4c1ece3db523de11546f487efc8ae39b01b35b5c))
- Add more logging to the freedesktop implementation ([`a94b4ce`](https://github.com/Byron/trash-rs/commit/a94b4ce160a4926d3cf777517ee6768a364b8310))
- Remove the Filesystem error kind ([`1138d8c`](https://github.com/Byron/trash-rs/commit/1138d8ccbc6e5bfd84daca86aacd9902326ecd3a))
- Don't run the CI for the nightly Rust ([`afa33ba`](https://github.com/Byron/trash-rs/commit/afa33badbab2473f649881d78c9acc49de376697))
- Fix clippy error ([`a182fbc`](https://github.com/Byron/trash-rs/commit/a182fbc7cd685151ff109c6a76623e27c2f666af))
- Fix freedesktop errors ([`afd17c3`](https://github.com/Byron/trash-rs/commit/afd17c3efd939283d20d2e130cfeb4b609adad42))
- Update the list example ([`a18d055`](https://github.com/Byron/trash-rs/commit/a18d055e684589eb2f9176ae6536ec433b023dc1))
- Fix warnings on macOS ([`f12cea9`](https://github.com/Byron/trash-rs/commit/f12cea96221d52c3809408009894fa28ac3b8a0c))
- Tweaked tests and documentation ([`330b1ec`](https://github.com/Byron/trash-rs/commit/330b1ec4f99376666ed48fb125e95e1928b1be0d))
- Documentation improvements ([`18337bf`](https://github.com/Byron/trash-rs/commit/18337bf73c3e89a90e793ba9b6e9741d558a019a))
- Rename `extra` to `os_limited` and other tweaks ([`29b6b11`](https://github.com/Byron/trash-rs/commit/29b6b113ffa4af0eeab6b147f5e19cee605e3274))
- Update the macOS backend ([`eff82e4`](https://github.com/Byron/trash-rs/commit/eff82e4e11195eb6871a08b5f607e9a0ab921a4c))
- Update the macos backend ([`e739014`](https://github.com/Byron/trash-rs/commit/e739014e08c4b883ff9350cbc1beef0e2da10797))
- Removed the silly PlatformApi error ([`61fa667`](https://github.com/Byron/trash-rs/commit/61fa667246cc83960d68f6ccfe2f29080ddb4186))
- Implement restore_all for windows ([`baa5171`](https://github.com/Byron/trash-rs/commit/baa5171c83a9f007e55dc2c2f412b7aad08815cc))
- Implement purge_all on Windows ([`9fc224d`](https://github.com/Byron/trash-rs/commit/9fc224db47b3bd4c34d6a34c3251dc495a076fe9))
- Remove the WinNull workaround ([`d8ab41f`](https://github.com/Byron/trash-rs/commit/d8ab41f97a25fdf81a20f0641c8a472e948a7f35))
- Ran cargo fmt ([`cf13e78`](https://github.com/Byron/trash-rs/commit/cf13e78e303da0a99fb01c801a030a9f1ff9d8af))
- Implement the `list` function for windows ([`6e77795`](https://github.com/Byron/trash-rs/commit/6e777954438acf6db2f0089d6a34fa0b77a60ab1))
- Implemented the delete function using `windows-rs` ([`218d0d0`](https://github.com/Byron/trash-rs/commit/218d0d00492833fdb301aeaaf1164b837a2a3af4))
- Fix example ([`69dbe38`](https://github.com/Byron/trash-rs/commit/69dbe386af48a1fb3d7a85fbb09235f263a9a5a2))
- Don't track the lockfile ([`942108d`](https://github.com/Byron/trash-rs/commit/942108d378c92edf387788d572f91808683b0019))
- Merge branch 'master' into v2-dev ([`c2d7a35`](https://github.com/Byron/trash-rs/commit/c2d7a35b7584f17e724853e6e3fdff9efeff5835))
- Minor adjustments ([`314e808`](https://github.com/Byron/trash-rs/commit/314e80823fccbb2b78558e9bea2f086e77fba26a))
## v1.3.0 (2021-01-24)
### Commit Statistics
- 18 commits contributed to the release.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Fix for clippy error ([`a728dce`](https://github.com/Byron/trash-rs/commit/a728dce614add4f3aa10c2b2721a4eb2a9e57cca))
- Increment version and update fmt ([`6d2270a`](https://github.com/Byron/trash-rs/commit/6d2270a0cbcebd8ebcc67a0278f81271e355bc63))
- Ran fmt and fix for warning ([`ff7cf3b`](https://github.com/Byron/trash-rs/commit/ff7cf3b09916c04ff861047db2b5005621d0597a))
- Fix for path canonicalization ([`5dfe5dc`](https://github.com/Byron/trash-rs/commit/5dfe5dc0beaa29a537808d017e5852ad976644e4))
- Merge pull request #23 from cbr9/optimize--get-desktop-environment ([`c887b6b`](https://github.com/Byron/trash-rs/commit/c887b6bdbe707320aada2478e5033f101e86aba6))
- Optimized get_desktop_environment() ([`a0a7fbb`](https://github.com/Byron/trash-rs/commit/a0a7fbbcd3e0e60b4b59066b65f3f4443ab57dbf))
- Update readme ([`30427f0`](https://github.com/Byron/trash-rs/commit/30427f04121bfbd8526d06deaf1d04cc7db145b0))
- Oops that Path wasn't completely unused after all ([`ba850ee`](https://github.com/Byron/trash-rs/commit/ba850eee27299e2aca0b1fc634f566f91a40e43b))
- Fixed compile warning and ran rustfmt ([`c556d28`](https://github.com/Byron/trash-rs/commit/c556d284887ae72bf95b6fafba357a59f982204d))
- Removed Cargo.lock from the gitignore. ([`cdde3a7`](https://github.com/Byron/trash-rs/commit/cdde3a7a34671b9ba26231361319f19459b75567))
- Implement `delete` and `delete_all` for macOS ([`cb564ef`](https://github.com/Byron/trash-rs/commit/cb564ef6efcd770cf96c527624da38b14db4b6ff))
- Updated readme ([`7a298be`](https://github.com/Byron/trash-rs/commit/7a298be45e22943206617eff9fbc2eca1234223c))
- Implement `delete` and `delete_all` for windows. ([`d9a25c8`](https://github.com/Byron/trash-rs/commit/d9a25c8f6addf87eb177184f24a444835fad0b4a))
- Add `delete` functions for Linux ([`fedeb83`](https://github.com/Byron/trash-rs/commit/fedeb8350625f252510ae5a2c5bb26fb74876b49))
- Update to the readme, incorporating some suggestions by Caleb Bassi ([`9bddccc`](https://github.com/Byron/trash-rs/commit/9bddccc2e8e368f7278135e60d41da601fa20aa4))
- Merge pull request #18 from cjbassi/rename-files ([`c49b496`](https://github.com/Byron/trash-rs/commit/c49b4961b1e83548777ea0a24cd99c7e6c6660fe))
- Rename readme and license files ([`5a9a5a6`](https://github.com/Byron/trash-rs/commit/5a9a5a66b53803b037636febc9265b66bcfc7334))
- Adds a deprecated attribute to the `is_implemented` function. ([`386db96`](https://github.com/Byron/trash-rs/commit/386db96e8eebed0b60d79ac055e8e312f01a605c))
## v1.1.0 (2020-08-12)
### Commit Statistics
- 2 commits contributed to the release.
- 87 days passed between releases.
- 0 commits were understood as [conventional](https://www.conventionalcommits.org).
- 1 unique issue was worked on: [#17](https://github.com/Byron/trash-rs/issues/17)
### Commit Details
view details
* **[#17](https://github.com/Byron/trash-rs/issues/17)**
- Implement std::error::Error for trash::Error ([`8765acf`](https://github.com/Byron/trash-rs/commit/8765acf6ef7a93db322baabb40df1edfc405b437))
* **Uncategorized**
- Increment minor version number ([`281bb93`](https://github.com/Byron/trash-rs/commit/281bb931159f22da85f4f23fcee92cc96e8a28e7))
## v1.0.1 (2020-05-16)
### Refactor
- port mac implementation to work with v2
Updates the existing Mac implementation to compile with v2 of the
library. Does not add any new functionality other defining required
methods.
Tests fail for methods relating to `list`, `purge_all`, or
`restore_all`, which are unimplemented.
### Commit Statistics
- 59 commits contributed to the release.
- 218 days passed between releases.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Update readme and increment the patch field in the version number. ([`217b473`](https://github.com/Byron/trash-rs/commit/217b4739d84827744a0b23a23b344e2118ac6f5b))
- Merge pull request #15 from myfreeweb/bsd ([`5af79ab`](https://github.com/Byron/trash-rs/commit/5af79aba26d767b1be1c816d18ff3ef7a7b3301d))
- Build "linux" module on *BSD (any non-macOS unix) ([`9e38ff8`](https://github.com/Byron/trash-rs/commit/9e38ff8ea89a70c9c3c87369413b3f798baa727d))
- Fix clippy warnings on linux. ([`0731a64`](https://github.com/Byron/trash-rs/commit/0731a6403f0854ee7098d4cd534bb435684ffd50))
- Merge branch 'master' of https://github.com/ArturKovacs/trash ([`1e43dc1`](https://github.com/Byron/trash-rs/commit/1e43dc1682e95ff497d6ce5d4b3185336227506f))
- Add .vscode to gitignore ([`f63f7ce`](https://github.com/Byron/trash-rs/commit/f63f7ce308dd3dad6c64e39d210891cc237704a8))
- Fix for clippy warning. ([`80eba00`](https://github.com/Byron/trash-rs/commit/80eba00727a72e47b016cda028a6ede7d218085f))
- Ran `cargo fmt`. ([`b753689`](https://github.com/Byron/trash-rs/commit/b7536891e17e796ab17a4070100024f1754e3ba0))
- Add tests for Windows and MacOS as well as the nightly toolchain. ([`29876c7`](https://github.com/Byron/trash-rs/commit/29876c7f96144c82f8bee565c80d433beaba548d))
- Default rust workflow (GitHub Actions) ([`cf7d22f`](https://github.com/Byron/trash-rs/commit/cf7d22fd2db44f52bbc48041edee66be391b2911))
- Merge branch 'master' into v2-dev ([`32de332`](https://github.com/Byron/trash-rs/commit/32de3324618619ffefe0cc7ed3c16bb8df647caf))
- Merge branch 'master' into v2-dev ([`b3ea819`](https://github.com/Byron/trash-rs/commit/b3ea819c48c619ff0b41df8357da0c4cd1da8d67))
- Remove Azure test ([`3e7db4f`](https://github.com/Byron/trash-rs/commit/3e7db4f45f81232567b9beb65860dd4a8651a6fa))
- Add GitHub Actions test ([`0c8b1fa`](https://github.com/Byron/trash-rs/commit/0c8b1fa561a574678d4db38fc3f0a10b470aca38))
- Fix wording in Readme ([`7025e10`](https://github.com/Byron/trash-rs/commit/7025e102110a00a290619a32822a7bbd823e3925))
- Update Readme to reflect the state of development. ([`a31a944`](https://github.com/Byron/trash-rs/commit/a31a94487c46e8ac2b886f59b79b5aa0be195fb8))
- Merge pull request #9 from NilsIrl/patch-1 ([`02bb739`](https://github.com/Byron/trash-rs/commit/02bb73950db23156680a2273498a1e815ab6fa3d))
- Fix typo ([`6c4d650`](https://github.com/Byron/trash-rs/commit/6c4d650fd8dd2346e20d37a17d6db9e4afd2ce77))
- Added test cases and extra documentation. The test cases cover empty input for `purge_all` and `restore_all`. ([`c275449`](https://github.com/Byron/trash-rs/commit/c275449adde03da9d7ba3064f4b72c9e816c2979))
- Moved Linux and Windows specific features to a mod ([`0e2fc93`](https://github.com/Byron/trash-rs/commit/0e2fc93f8bb5c17a9c5c89849453b04344995cab))
- Refined windows implementation. Added error kind `RestoreTwins`. ([`d105553`](https://github.com/Byron/trash-rs/commit/d1055538c16e318247b2817cce85ec522b2163a2))
- Ran `cargo fmt` ([`743b2f3`](https://github.com/Byron/trash-rs/commit/743b2f3f889b0519275b9d52475b0b68c661382d))
- Merge branch 'v2-mac' into v2-dev ([`62a7218`](https://github.com/Byron/trash-rs/commit/62a7218644511d75e7b4d162c70aac2f1a4625e9))
- Merge branch 'v2-dev' of https://github.com/ArturKovacs/trash into v2-dev ([`90d3fa6`](https://github.com/Byron/trash-rs/commit/90d3fa62eb7ca525e6fc45c48cdeba95322a7416))
- Removed the two previously added errors. Replaced `ZeroMountPointsFound` and `CantOpenMountPointsFile` with `panic!` after coming across https://lukaskalbertodt.github.io/2019/11/14/thoughts-on-error-handling-in-rust.html and reading http://joeduffyblog.com/2016/02/07/the-error-model/ ([`fa03282`](https://github.com/Byron/trash-rs/commit/fa0328204662ea871336087738642a6c9dece33e))
- No need for those parentheses ([`534677e`](https://github.com/Byron/trash-rs/commit/534677ed4c23316f956dc43e84f16ca68f744331))
- Merge branch 'v2-dev' of https://github.com/ArturKovacs/trash into v2-dev ([`00fc235`](https://github.com/Byron/trash-rs/commit/00fc235235d4f00e34ffb8cd04b975157037ab91))
- Add missing error kinds. `ZeroMountPointsFound` and `CantOpenMountPointsFile` were added. ([`6c79f7d`](https://github.com/Byron/trash-rs/commit/6c79f7d95604c68363cd710f99150e655152dbc4))
- Improve collision handling and add collision test. ([`6db249b`](https://github.com/Byron/trash-rs/commit/6db249bef9529eab4f8325dbc36e84a47000525b))
- Merge pull request #7 from ArturKovacs/v2-linux ([`b957f38`](https://github.com/Byron/trash-rs/commit/b957f3894b6d06b42cc48824a125825b954496c4))
- Remove debug lines. ([`3ab9217`](https://github.com/Byron/trash-rs/commit/3ab9217280b6656b4a861e61cc590c326c008d67))
- Fix creating the home trash folder. ([`10364c6`](https://github.com/Byron/trash-rs/commit/10364c64508acd4fb564cc761bc746eb2d9dd4b1))
- Create home trash if doesn't yet exist. Also added debug print line numbers. ([`e1b2aae`](https://github.com/Byron/trash-rs/commit/e1b2aaeb8fd00d07be250208767ba85217d55010))
- Attempt to add RUST_BACKTRACE=1 again. ([`7bd6023`](https://github.com/Byron/trash-rs/commit/7bd6023710e60ddbf6f9f15bcf1bd714f6aaedd9))
- Merge branch 'v2-dev' into v2-linux ([`303a274`](https://github.com/Byron/trash-rs/commit/303a274989d0a6b6faf7eab3f0ae9149e6e86d71))
- Added RUST_BACKTRACE=1 to test. ([`c1cb106`](https://github.com/Byron/trash-rs/commit/c1cb10611be762cb13fce4fd38c39961ad84317a))
- Merge branch 'v2-dev' into v2-linux ([`ecf521f`](https://github.com/Byron/trash-rs/commit/ecf521fbccaae27b4eb160598448341d5e8b7700))
- Add ability to trash items from an external drive. ([`16f0ee1`](https://github.com/Byron/trash-rs/commit/16f0ee19beaf116b9dfd44de06b272f8b62fb3fd))
- Added a partialy implementaiton of `remove_all`. Can't remove from non-root devices or partitions. ([`1e03167`](https://github.com/Byron/trash-rs/commit/1e031679d6a3c0cd8a780014d3331bd4fd8bcd1d))
- Steps towards implementing `remove_all`. ([`20ba354`](https://github.com/Byron/trash-rs/commit/20ba354399d2ce96e7ee624bbf2a03407df163db))
- Fix for `list` failing on Linux. This happened because `list` on Linux didn't handle paralell threads manipulating the trash correctly. ([`d6cb6ba`](https://github.com/Byron/trash-rs/commit/d6cb6bac6758a0020b88ef5632bf9f06f748f7ca))
- Merge pull request #6 from ayazhafiz/refactor/mac2 ([`adf0ea4`](https://github.com/Byron/trash-rs/commit/adf0ea4fab0ace99b443c5498ba5495c89abcd30))
- Remove the Cirrus CI config. ([`fd597fc`](https://github.com/Byron/trash-rs/commit/fd597fc852eddb23472276be6c638e6e40281f67))
- Port mac implementation to work with v2 ([`576fad7`](https://github.com/Byron/trash-rs/commit/576fad719cb240203dec030890d54fe416a42edd))
- Add MacOS and Linux as targets for CI tests. ([`e409a98`](https://github.com/Byron/trash-rs/commit/e409a983b64d36c2b585ecdb5374357a34f5da53))
- Fix OS setup in Azure's config. ([`08db817`](https://github.com/Byron/trash-rs/commit/08db8172080832727bd5002e394054c34c5147ea))
- Update Azure's target operating systems. ([`cfb25b6`](https://github.com/Byron/trash-rs/commit/cfb25b6e0d9e8fd00379871760430009c52289cd))
- Add Azure Pipelines CI. ([`760dfa6`](https://github.com/Byron/trash-rs/commit/760dfa64e53a2e1228230c073bd551acb868b286))
- Add Cirrus CI test. ([`e5c22c4`](https://github.com/Byron/trash-rs/commit/e5c22c4567e2f289a918affc46fa923a962799cf))
- Added implementation of purge_all for Linux. Also ran rustfmt and created a rustfmt config. ([`a90f9bf`](https://github.com/Byron/trash-rs/commit/a90f9bfa0d19fd6fad88c91bbd0e6a46c4661a0e))
- Now using the url crate for parsing the original location on Linux. ([`02ffe0b`](https://github.com/Byron/trash-rs/commit/02ffe0b336136c730213670d4c5b6eb04addaa55))
- Add `list` implementation for linux. ([`5c73fea`](https://github.com/Byron/trash-rs/commit/5c73fea22341c520103564953535c84b7271fc4e))
- Add note about coming features in version 2 to the Readme. ([`dddbe25`](https://github.com/Byron/trash-rs/commit/dddbe25171e6f93ffd2b80627d25e8313ff21498))
- Improve the Error type and add `create_remove_empty_folder` test. ([`a940b66`](https://github.com/Byron/trash-rs/commit/a940b66abea7769aba8e0b1d99995b8174239877))
- Changed `std::mem::uninit` and `std::mem::zeroed` to `std::mem::MaybeUninit`. Plus ran Rustfmt. ([`632a6fb`](https://github.com/Byron/trash-rs/commit/632a6fb31fc4c4bee751f0537ca317fe9f933f5c))
- Now `purge_all` doesn't show a dialog on windows. ([`07e3bc2`](https://github.com/Byron/trash-rs/commit/07e3bc25832a49af13ee3c2a1fdc1f425fce8805))
- Fix `purge_all` and `restore_all` reading invvalid memory and not executing the operation on the requested items. Add test cases for `purge_all` and `restore_all`. Test are now thread safe. ([`22d5181`](https://github.com/Byron/trash-rs/commit/22d51813759c129e87625eef5d068a1481bfbdb8))
- Implement `purge_all` and `restore_all` for Windows. ([`e06c825`](https://github.com/Byron/trash-rs/commit/e06c825e93ce9774733cfcf539e3c7e928cdb8cc))
- Run rust fmt. Implement `list` for Windows. ([`3f29636`](https://github.com/Byron/trash-rs/commit/3f29636a978fd5a462db1588040d794d81648be7))
## v1.0.0 (2019-10-11)
### Refactor
- port mac implementation to work with v2
Updates the existing Mac implementation to compile with v2 of the
library. Does not add any new functionality other defining required
methods.
Tests fail for methods relating to `list`, `purge_all`, or
`restore_all`, which are unimplemented.
### New Features
- implementation for macOS
Moves files to trash on macOS by executing an AppleScript command to
delete all requested paths.
### Commit Statistics
- 19 commits contributed to the release over the course of 99 calendar days.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Updated version number and readme ([`79ee69e`](https://github.com/Byron/trash-rs/commit/79ee69e3e12a9a66146897ab432f29eaa8ac2d28))
- Merge pull request #1 from ayazhafiz/feat/mac ([`48a6b11`](https://github.com/Byron/trash-rs/commit/48a6b11cae520ca1b60c42270912402c1d51c018))
- Implementation for macOS ([`d68cc2a`](https://github.com/Byron/trash-rs/commit/d68cc2aedee5e8316117bec257975da30cbd7483))
- Fix wrong code references in the linux implementation. ([`037fed8`](https://github.com/Byron/trash-rs/commit/037fed8ae6b5ed76cec00037cdc8340d7787d7cb))
- Add docs badge to readme ([`88261d5`](https://github.com/Byron/trash-rs/commit/88261d5af0b165a06483ea07e5aa378d2223d067))
- Increment version number ([`f758543`](https://github.com/Byron/trash-rs/commit/f75854358b7c8dea23aec6f40362fab4039d9659))
- Improve readme. Add remove_all function to mac as unimplemented. ([`2850270`](https://github.com/Byron/trash-rs/commit/2850270004cea47718b18aa3d3b290263ba7b8e3))
- Merge branch 'master' of https://github.com/ArturKovacs/trash ([`a651d0f`](https://github.com/Byron/trash-rs/commit/a651d0f3b7c261da9bd2fd65f16166b43d63abf3))
- Add folder remove test ([`b7bb22f`](https://github.com/Byron/trash-rs/commit/b7bb22f7b21027ad73d53eded480921f3346a14c))
- Updated the Cargo.toml ([`1ec1ef9`](https://github.com/Byron/trash-rs/commit/1ec1ef96b941bbab6672a86b24b0e23afdfe2165))
- Add license ([`1725e61`](https://github.com/Byron/trash-rs/commit/1725e612e103dda2e574543ea90821934ed46ae6))
- Add doc comments ([`b16a1d3`](https://github.com/Byron/trash-rs/commit/b16a1d3dff8c3b4d9b24b6dc87c9e3781b667c58))
- Fix Windows compile error. ([`15e801e`](https://github.com/Byron/trash-rs/commit/15e801e1a242e1b0263fe854e6c9d58a68774dd0))
- Add the `remove_all` function. ([`f033dc3`](https://github.com/Byron/trash-rs/commit/f033dc308ee061adc579f7426697c5cb3c280956))
- Minor refactoring. ([`9c7363d`](https://github.com/Byron/trash-rs/commit/9c7363dbeae19edb8079a57fcc93d012c8064ef0))
- Add Linux support. ([`0429d3b`](https://github.com/Byron/trash-rs/commit/0429d3bf1e7f09e97eae4aefde0cb8c8e283b235))
- Fixed platform specific compilation ([`84cba5b`](https://github.com/Byron/trash-rs/commit/84cba5b11acf02d5ba99d776529b93bac54f8094))
- Changed required winapi version. ([`d49bce8`](https://github.com/Byron/trash-rs/commit/d49bce8d346e10c08910520383ed4054a3948535))
- Initial ([`4c23314`](https://github.com/Byron/trash-rs/commit/4c233148288711419a04fdfa96e36dcb77f0469f))
trash-5.2.0/Cargo.lock 0000644 00000052350 00000000001 0010141 0 ustar # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
"objc2",
]
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-targets 0.52.5",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "hermit-abi"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core 0.52.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "js-sys"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]]
name = "objc2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
dependencies = [
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-encode"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8"
[[package]]
name = "objc2-foundation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.5.0",
"block2",
"libc",
"objc2",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.5.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serial_test"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d"
dependencies = [
"dashmap",
"lazy_static",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "trash"
version = "5.2.0"
dependencies = [
"chrono",
"env_logger",
"libc",
"log",
"objc2",
"objc2-foundation",
"once_cell",
"rand",
"scopeguard",
"serial_test",
"tempfile",
"urlencoding",
"windows",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core 0.56.0",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-implement"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-result"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b"
dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
trash-5.2.0/Cargo.toml 0000644 00000006675 00000000001 0010175 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "trash"
version = "5.2.0"
authors = ["Artur Kovacs "]
build = false
include = [
"src/**/*",
"LICENSE.txt",
"README.md",
"CHANGELOG.md",
"build.rs",
]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A library for moving files and folders to the Recycle Bin"
readme = "README.md"
keywords = [
"remove",
"trash",
"rubbish",
"recycle",
"bin",
]
license = "MIT"
repository = "https://github.com/ArturKovacs/trash"
[package.metadata.cross.target.x86_64-unknown-netbsd]
pre-build = [
"mkdir -p /tmp/netbsd",
"curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O",
"tar -C /tmp/netbsd -xJf base.tar.xz",
"cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib",
"rm base.tar.xz",
"rm -rf /tmp/netbsd",
]
[lib]
name = "trash"
path = "src/lib.rs"
[dependencies.log]
version = "0.4"
[dev-dependencies.chrono]
version = "0.4.31"
features = ["clock"]
default-features = false
[dev-dependencies.env_logger]
version = "0.10.0"
[dev-dependencies.once_cell]
version = "1.18.0"
[dev-dependencies.rand]
version = "0.8.5"
[dev-dependencies.serial_test]
version = "2.0.0"
default-features = false
[dev-dependencies.tempfile]
version = "3.8.0"
[features]
coinit_apartmentthreaded = []
coinit_disable_ole1dde = []
coinit_multithreaded = []
coinit_speed_over_memory = []
default = [
"coinit_apartmentthreaded",
"chrono",
]
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies.chrono]
version = "0.4.31"
features = ["clock"]
optional = true
default-features = false
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies.libc]
version = "0.2.149"
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies.once_cell]
version = "1.18.0"
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies.scopeguard]
version = "1.2.0"
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies.urlencoding]
version = "2.1.3"
[target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.once_cell]
version = "1.7.2"
[target.'cfg(target_os = "macos")'.dependencies.objc2]
version = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.0"
features = [
"NSError",
"NSFileManager",
"NSString",
"NSURL",
]
[target."cfg(windows)".dependencies.scopeguard]
version = "1.2.0"
[target."cfg(windows)".dependencies.windows]
version = "0.56.0"
features = [
"Win32_Foundation",
"Win32_Storage_EnhancedStorage",
"Win32_System_Com_StructuredStorage",
"Win32_System_SystemServices",
"Win32_UI_Shell_PropertiesSystem",
]
trash-5.2.0/Cargo.toml.orig 0000644 0000000 0000000 00000004250 10461020230 0013641 0 ustar 0000000 0000000
[package]
name = "trash"
version = "5.2.0"
authors = ["Artur Kovacs "]
license = "MIT"
readme = "README.md"
description = "A library for moving files and folders to the Recycle Bin"
keywords = ["remove", "trash", "rubbish", "recycle", "bin"]
repository = "https://github.com/ArturKovacs/trash"
edition = "2021"
include = ["src/**/*", "LICENSE.txt", "README.md", "CHANGELOG.md", "build.rs"]
[features]
default = ["coinit_apartmentthreaded", "chrono"]
coinit_apartmentthreaded = []
coinit_multithreaded = []
coinit_disable_ole1dde = []
coinit_speed_over_memory = []
[dependencies]
log = "0.4"
[dev-dependencies]
serial_test = { version = "2.0.0", default-features = false }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
rand = "0.8.5"
once_cell = "1.18.0"
env_logger = "0.10.0"
tempfile = "3.8.0"
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.5.1"
objc2-foundation = { version = "0.2.0", features = [
"NSError",
"NSFileManager",
"NSString",
"NSURL",
] }
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies]
chrono = { version = "0.4.31", optional = true, default-features = false, features = [
"clock",
] }
libc = "0.2.149"
scopeguard = "1.2.0"
urlencoding = "2.1.3"
once_cell = "1.18.0"
[target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
once_cell = "1.7.2"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.56.0", features = [
"Win32_Foundation",
"Win32_Storage_EnhancedStorage",
"Win32_System_Com_StructuredStorage",
"Win32_System_SystemServices",
"Win32_UI_Shell_PropertiesSystem",
] }
scopeguard = "1.2.0"
# workaround for https://github.com/cross-rs/cross/issues/1345
[package.metadata.cross.target.x86_64-unknown-netbsd]
pre-build = [
"mkdir -p /tmp/netbsd",
"curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O",
"tar -C /tmp/netbsd -xJf base.tar.xz",
"cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib",
"rm base.tar.xz",
"rm -rf /tmp/netbsd",
]
trash-5.2.0/LICENSE.txt 0000644 0000000 0000000 00000002050 10461020230 0012571 0 ustar 0000000 0000000 Copyright 2019 Artúr Barnabás Kovács
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
trash-5.2.0/README.md 0000644 0000000 0000000 00000002453 10461020230 0012234 0 ustar 0000000 0000000
[](https://crates.io/crates/trash)
[](https://docs.rs/trash)
[](https://github.com/Byron/trash-rs/actions/workflows/rust.yml)
## About
The `trash` is a Rust library for moving files and folders to the operating system's Recycle Bin or Trash or Rubbish Bin or what have you :D
The library supports Windows, macOS, and all FreeDesktop Trash compliant environments (including GNOME, KDE, XFCE, and more).
See more about the FreeDesktop Trash implementation in the `freedesktop.rs` file.
## Usage
```toml
# In Cargo.toml
[dependencies]
trash = "3"
```
```rust
// In main.rs
use std::fs::File;
use trash;
fn main() {
// Let's create and remove a single file
File::create("remove-me").unwrap();
trash::delete("remove-me").unwrap();
assert!(File::open("remove-me").is_err());
// Now let's remove multiple files at once
let the_others = ["remove-me-too", "dont-forget-about-me-either"];
for name in the_others.iter() {
File::create(name).unwrap();
}
trash::delete_all(&the_others).unwrap();
for name in the_others.iter() {
assert!(File::open(name).is_err());
}
}
```
trash-5.2.0/src/freedesktop.rs 0000644 0000000 0000000 00000136776 10461020230 0014445 0 ustar 0000000 0000000 //! This implementation will manage the trash according to the Freedesktop Trash specification,
//! version 1.0 found at
//!
//! Most -if not all- Linux based desktop operating systems implement the Trash according to this specification.
//! In other words: I looked, but I could not find any Linux based desktop OS that used anything else than the
//! Freedesktop Trash specification.
//!
use std::{
borrow::{Borrow, Cow},
collections::HashSet,
ffi::{OsStr, OsString},
fs::{self, File, OpenOptions},
io::{BufRead, BufReader, Write},
os::unix::{
ffi::{OsStrExt, OsStringExt},
fs::PermissionsExt,
},
path::{Component, Path, PathBuf},
};
use log::{debug, warn};
use crate::{Error, TrashContext, TrashItem, TrashItemMetadata, TrashItemSize};
type FsError = (PathBuf, std::io::Error);
#[derive(Clone, Default, Debug)]
pub struct PlatformTrashContext;
impl PlatformTrashContext {
pub const fn new() -> Self {
PlatformTrashContext
}
}
impl TrashContext {
pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec) -> Result<(), 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() };
for path in full_paths {
debug!("Deleting {:?}", path);
let topdir = get_first_topdir_containing_path(&path, &sorted_mount_points);
debug!("The topdir of this file is {:?}", topdir);
if topdir == home_topdir {
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))?;
} 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))?;
} else {
execute_on_mounted_trash_folders(uid, topdir, true, true, |trash_path| {
move_to_trash(&path, trash_path, topdir)
})
.map_err(|(p, e)| fs_error(p, e))?;
}
}
Ok(())
}
}
pub fn list() -> Result, Error> {
let EvaluatedTrashFolders { trash_folders, home_error, sorted_mount_points } = eval_trash_folders()?;
if trash_folders.is_empty() {
warn!("No trash folder was found. The error when looking for the 'home trash' was: {:?}", home_error);
return Ok(vec![]);
}
// List all items from the set of trash folders
let mut result = Vec::new();
for folder in &trash_folders {
// Read the info files for every file
let top_dir = get_first_topdir_containing_path(folder, &sorted_mount_points);
let info_folder = folder.join("info");
if !info_folder.is_dir() {
warn!("The path {:?} did not point to a directory, skipping this trash folder.", info_folder);
continue;
}
let read_dir = match std::fs::read_dir(&info_folder) {
Ok(d) => d,
Err(e) => {
// After all the earlier checks, it's still possible that the directory does not exist at this point (or is not readable)
// because another process may have deleted it or modified its access rights in the meantime.
// So let's just pring a warning and continue to the rest of the folders
warn!("The trash info folder {:?} could not be read. Error was {:?}", info_folder, e);
continue;
}
};
#[cfg_attr(not(feature = "chrono"), allow(unused_labels))]
'trash_item: for entry in read_dir {
let info_entry = match entry {
Ok(entry) => entry,
Err(e) => {
// Another thread or process may have removed that entry by now
debug!("Tried resolving the trash info `DirEntry` but it failed with: '{}'", e);
continue;
}
};
// Entrty should really be an info file but better safe than sorry
let file_type = match info_entry.file_type() {
Ok(f_type) => f_type,
Err(e) => {
// Another thread or process may have removed that entry by now
debug!("Tried getting the file type of the trash info `DirEntry` but failed with: {}", e);
continue;
}
};
let info_path = info_entry.path();
if !file_type.is_file() {
warn!("Found an item that's not a file, among the trash info files. This is unexpected. The path to the item is: '{:?}'", info_path);
continue;
}
let info_file = match File::open(&info_path) {
Ok(file) => file,
Err(e) => {
// Another thread or process may have removed that entry by now
debug!("Tried opening the trash info '{:?}' but failed with: {}", info_path, e);
continue;
}
};
let id = info_path.clone().into();
let mut name = None;
let mut original_parent: Option = None;
#[cfg_attr(not(feature = "chrono"), allow(unused_mut))]
let mut time_deleted = None;
let info_reader = BufReader::new(info_file);
// Skip 1 because the first line must be "[Trash Info]"
'info_lines: for line_result in info_reader.lines().skip(1) {
// Another thread or process may have removed the infofile by now
let line = if let Ok(line) = line_result {
line
} else {
break 'info_lines;
};
let mut split = line.split('=');
// Just unwraping here because the system is assumed to follow the specification.
let key = split.next().unwrap().trim();
let value = split.next().unwrap().trim();
if key == "Path" {
let value_path = {
let path = Path::new(value);
if path.is_relative() {
decode_uri_path(top_dir.join(path))
} else {
decode_uri_path(path)
}
};
name = value_path.file_name().map(|name| name.to_owned());
let parent = value_path.parent().expect("Absolute path to trashed item should have a parent");
original_parent = Some(parent.into());
} else if key == "DeletionDate" {
#[cfg(feature = "chrono")]
{
use chrono::{NaiveDateTime, TimeZone};
let parsed_time = NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S");
let naive_local = match parsed_time {
Ok(t) => t,
Err(e) => {
log::error!("Failed to parse the deletion date of the trash item {:?}. The deletion date was '{}'. Parse error was: {:?}", name, value, e);
continue 'trash_item;
}
};
let time = chrono::Local.from_local_datetime(&naive_local).earliest();
match time {
Some(time) => time_deleted = Some(time.timestamp()),
None => {
log::error!(
"Failed to convert the local time to a UTC time. Local time was {:?}",
naive_local
);
continue 'trash_item;
}
}
}
}
}
if let Some(name) = name {
if let Some(original_parent) = original_parent {
if time_deleted.is_none() {
warn!("Could not determine the deletion time of the trash item. (The `DeletionDate` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
result.push(TrashItem { id, name, original_parent, time_deleted: time_deleted.unwrap_or(-1) });
} else {
warn!("Could not determine the original parent folder of the trash item. (The `Path` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
} else {
warn!("Could not determine the name of the trash item. (The `Path` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
}
}
Ok(result)
}
pub fn is_empty() -> Result {
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, Error> {
let EvaluatedTrashFolders { trash_folders, home_error, .. } = eval_trash_folders()?;
if trash_folders.is_empty() {
return match home_error {
Some(e) => Err(e),
None => Err(Error::Unknown {
description: "Could not find a valid 'home trash' nor valid trashes on other mount points".into(),
}),
};
}
Ok(trash_folders)
}
struct EvaluatedTrashFolders {
trash_folders: HashSet,
home_error: Option,
sorted_mount_points: Vec,
}
fn eval_trash_folders() -> Result {
let mut trash_folders = HashSet::new();
// Get home trash folder and add it to the set of trash folders.
// It may not exist and that's completely fine as long as there are other trash folders.
let home_error;
match home_trash() {
Ok(home_trash) => {
if !home_trash.is_dir() {
home_error = Some(Error::Unknown {
description:
"The 'home trash' either does not exist or is not a directory (or a link pointing to a dir)"
.into(),
});
} else {
trash_folders.insert(home_trash);
home_error = None;
}
}
Err(e) => {
home_error = Some(e);
}
}
// Get all mount-points and attempt to find a trash folder in each adding them to the SET of
// trash folders when found one.
let uid = unsafe { libc::getuid() };
let sorted_mount_points = get_sorted_mount_points()?;
for mount in &sorted_mount_points {
execute_on_mounted_trash_folders(uid, &mount.mnt_dir, false, false, |trash_path| {
trash_folders.insert(trash_path);
Ok(())
})
.map_err(|(p, e)| fs_error(p, e))?;
}
Ok(EvaluatedTrashFolders { trash_folders, home_error, sorted_mount_points })
}
pub fn metadata(item: &TrashItem) -> Result {
// When purging an item the "in-trash" filename must be parsed from the trashinfo filename
// which is the filename in the `id` field.
let info_file = &item.id;
let file = restorable_file_in_trash_from_info_file(info_file);
assert!(virtually_exists(&file).map_err(|e| fs_error(&file, e))?);
let metadata = fs::symlink_metadata(&file).map_err(|e| fs_error(&file, e))?;
let is_dir = metadata.is_dir();
let size = if is_dir {
TrashItemSize::Entries(fs::read_dir(&file).map_err(|e| fs_error(&file, e))?.count())
} else {
TrashItemSize::Bytes(metadata.len())
};
Ok(TrashItemMetadata { size })
}
/// The path points to:
/// - existing file | directory | symlink => Ok(true)
/// - broken symlink => Ok(true)
/// - nothing => Ok(false)
/// - I/O Error => Err(Io)
#[inline]
fn virtually_exists(path: &Path) -> std::io::Result {
Ok(path.try_exists()? || path.is_symlink())
}
pub fn purge_all(items: I) -> Result<(), Error>
where
I: IntoIterator,
::Item: Borrow,
{
for item in items.into_iter() {
// When purging an item the "in-trash" filename must be parsed from the trashinfo filename
// which is the filename in the `id` field.
let info_file = &item.borrow().id;
// A bunch of unwraps here. This is fine because if any of these fail that means
// that either there's a bug in this code or the target system didn't follow
// the specification.
let file = restorable_file_in_trash_from_info_file(info_file);
if file.is_dir() {
std::fs::remove_dir_all(&file).map_err(|e| fs_error(&file, e))?;
// TODO Update directory size cache if there's one.
} else {
std::fs::remove_file(&file).map_err(|e| fs_error(&file, e))?;
}
std::fs::remove_file(info_file).map_err(|e| fs_error(info_file, e))?;
}
Ok(())
}
fn restorable_file_in_trash_from_info_file(info_file: impl AsRef) -> PathBuf {
let info_file = info_file.as_ref();
let trash_folder = Path::new(info_file).parent().unwrap().parent().unwrap();
let name_in_trash = Path::new(info_file).file_stem().unwrap();
trash_folder.join("files").join(name_in_trash)
}
pub fn restore_all(items: I) -> Result<(), Error>
where
I: IntoIterator- ,
{
// Simply read the items' original location from the infofile and attemp to move the items there
// and delete the infofile if the move operation was sucessful.
let mut iter = items.into_iter();
while let Some(item) = iter.next() {
// The "in-trash" filename must be parsed from the trashinfo filename
// which is the filename in the `id` field.
let info_file = &item.id;
// A bunch of unwraps here. This is fine because if any of these fail that means
// that either there's a bug in this code or the target system didn't follow
// the specification.
let file = restorable_file_in_trash_from_info_file(info_file);
assert!(virtually_exists(&file).map_err(|e| fs_error(&file, e))?);
// TODO add option to forcefully replace any target at the restore location
// if it already exists.
let original_path = item.original_path();
// Make sure the parent exists so that `create_dir` doesn't faile due to that.
std::fs::create_dir_all(&item.original_parent).map_err(|e| fs_error(&item.original_parent, e))?;
let mut collision = false;
if file.is_dir() {
// NOTE create_dir_all succeeds when the path already exist but create_dir
// fails with `std::io::ErrorKind::AlreadyExists`.
if let Err(e) = std::fs::create_dir(&original_path) {
if e.kind() == std::io::ErrorKind::AlreadyExists {
collision = true;
} else {
return Err(fs_error(&original_path, e));
}
}
} else {
// File or symlink
if let Err(e) = OpenOptions::new().create_new(true).write(true).open(&original_path) {
if e.kind() == std::io::ErrorKind::AlreadyExists {
collision = true;
} else {
return Err(fs_error(&original_path, e));
}
}
}
if collision {
let remaining: Vec<_> = std::iter::once(item).chain(iter).collect();
return Err(Error::RestoreCollision { path: original_path, remaining_items: remaining });
}
std::fs::rename(&file, &original_path).map_err(|e| fs_error(&file, e))?;
std::fs::remove_file(info_file).map_err(|e| fs_error(info_file, e))?;
}
Ok(())
}
/// According to the specification (see at the top of the file) there are two kinds of
/// trash-folders for a mounted drive or partition.
/// 1, .Trash/uid
/// 2, .Trash-uid
///
/// This function executes `op` providing it with a
/// trash-folder path that's associated with the partition mounted at `topdir`.
///
fn execute_on_mounted_trash_folders Result<(), FsError>>(
uid: u32,
topdir: impl AsRef,
first_only: bool,
create_folder: bool,
mut op: F,
) -> Result<(), FsError> {
// See if there's a ".Trash" directory at the mounted location
let topdir = topdir.as_ref();
let trash_path = topdir.join(".Trash");
if trash_path.is_dir() {
let validity = folder_validity(&trash_path)?;
if validity == TrashValidity::Valid {
let users_trash_path = trash_path.join(uid.to_string());
if users_trash_path.exists() && users_trash_path.is_dir() {
op(users_trash_path)?;
if first_only {
return Ok(());
}
}
} else {
warn!("A Trash folder was found at '{:?}', but it's invalid because it's {:?}", trash_path, validity);
}
}
// See if there's a ".Trash-$UID" directory at the mounted location
let trash_path = topdir.join(format!(".Trash-{uid}"));
let should_execute;
if !trash_path.exists() || !trash_path.is_dir() {
if create_folder {
std::fs::create_dir(&trash_path).map_err(|e| (trash_path.to_owned(), e))?;
should_execute = true;
} else {
should_execute = false;
}
} else {
should_execute = true;
}
if should_execute {
op(trash_path)?;
}
Ok(())
}
fn move_to_trash(
src: impl AsRef,
trash_folder: impl AsRef,
_topdir: impl AsRef,
) -> Result<(), FsError> {
let src = src.as_ref();
let trash_folder = trash_folder.as_ref();
let files_folder = trash_folder.join("files");
let info_folder = trash_folder.join("info");
// Ensure the `files` and `info` folders exist
std::fs::create_dir_all(&files_folder).map_err(|e| (files_folder.to_owned(), e))?;
std::fs::create_dir_all(&info_folder).map_err(|e| (info_folder.to_owned(), e))?;
// This kind of validity must only apply ot administrator style trash folders
// See Trash directories, (1) at https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
//assert_eq!(folder_validity(trash_folder)?, TrashValidity::Valid);
// When trashing a file one must make sure that every trashed item is uniquely named.
// However the `rename` function -that is used in *nix systems to move files- by default
// overwrites the destination. Therefore when multiple threads are removing items with identical
// names, an implementation might accidently overwrite an item that was just put into the trash
// if it's not careful enough.
//
// The strategy here is to use the `create_new` parameter of `OpenOptions` to
// try creating a placeholder file in the trash but don't do so if one with an identical name
// already exist. This newly created empty file can then be safely overwritten by the src file
// using the `rename` function.
let filename = src.file_name().unwrap();
let mut appendage = 0usize;
loop {
appendage += 1;
let in_trash_name: Cow<'_, OsStr> = if appendage > 1 {
let mut trash_name = filename.to_owned();
trash_name.push(format!(".{appendage}"));
trash_name.into()
} else {
filename.into()
};
// Length of name + length of '.trashinfo'
let mut info_name = OsString::with_capacity(in_trash_name.len() + 10);
info_name.push(&in_trash_name);
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);
match info_result {
Err(error) => {
if error.kind() == std::io::ErrorKind::AlreadyExists {
continue;
} else {
debug!("Failed to create the new file {:?}", info_file_path);
return Err((info_file_path, error));
}
}
Ok(mut file) => {
debug!("Successfully created {:?}", info_file_path);
// Write the info file before actually moving anything
writeln!(file, "[Trash Info]")
.and_then(|_| {
let absolute_uri = encode_uri_path(src);
writeln!(file, "Path={absolute_uri}").and_then(|_| {
#[cfg(feature = "chrono")]
{
let now = chrono::Local::now();
writeln!(file, "DeletionDate={}", now.format("%Y-%m-%dT%H:%M:%S"))
}
#[cfg(not(feature = "chrono"))]
{
Ok(())
}
})
})
.map_err(|e| (info_file_path.to_owned(), e))?;
}
}
let path = files_folder.join(&in_trash_name);
match move_items_no_replace(src, &path) {
Err((path, error)) => {
debug!("Failed moving item to the trash (this is usually OK). {:?}", error);
// Try to delete the info file
if let Err(info_err) = std::fs::remove_file(info_file_path) {
warn!("Created the trash info file, then failed to move the item to the trash. So far it's OK, but then failed remove the initial info file. There's either a bug in this program or another faulty program is manupulating the Trash. The error was: {:?}", info_err);
}
if error.kind() == std::io::ErrorKind::AlreadyExists {
continue;
} else {
return Err((path, error));
}
}
Ok(_) => {
// We did it!
break;
}
}
}
Ok(())
}
/// An error may mean that a collision was found.
fn move_items_no_replace(src: impl AsRef, dst: impl AsRef) -> Result<(), FsError> {
let src = src.as_ref();
let dst = dst.as_ref();
try_creating_placeholders(src, dst)?;
std::fs::rename(src, dst).map_err(|e| (src.to_owned(), e))?;
// Once everything is moved, lets recursively remove the directory
if src.is_dir() {
std::fs::remove_dir_all(src).map_err(|e| (src.to_owned(), e))?;
}
Ok(())
}
fn try_creating_placeholders(src: impl AsRef, dst: impl AsRef) -> Result<(), FsError> {
let src = src.as_ref();
let dst = dst.as_ref();
let metadata = src.symlink_metadata().map_err(|e| (src.to_owned(), e))?;
if metadata.is_dir() {
// NOTE create_dir fails if the directory already exists
std::fs::create_dir(dst).map_err(|e| (dst.to_owned(), e))?;
} else {
// Symlink or file
OpenOptions::new().create_new(true).write(true).open(dst).map_err(|e| (dst.to_owned(), e))?;
}
Ok(())
}
fn decode_uri_path(path: impl AsRef) -> PathBuf {
// Paths may be invalid Unicode on most Unixes so they should be treated as byte strings
// A higher level crate, such as `url`, can't be used directly since its API intakes valid Rust
// strings. Thus, the easiest way is to manually decode each segment of the path and recombine.
path.as_ref().iter().map(|part| OsString::from_vec(urlencoding::decode_binary(part.as_bytes()).to_vec())).collect()
}
fn encode_uri_path(path: impl AsRef) -> String {
// `iter()` cannot be used here because it yields '/' in certain situations, such as
// for root directories.
// Slashes would be encoded and thus mess up the path
let path: PathBuf = path
.as_ref()
.components()
.map(|component| {
// Only encode names and not '/', 'C:\', et cetera
if let Component::Normal(part) = component {
urlencoding::encode_binary(part.as_bytes()).to_string()
} else {
component.as_os_str().to_str().expect("Path components such as '/' are valid Unicode").to_owned()
}
})
.collect();
path.to_str().expect("URL encoded bytes is valid Unicode").to_owned()
}
#[derive(Eq, PartialEq, Debug)]
enum TrashValidity {
Valid,
InvalidSymlink,
InvalidNotSticky,
}
fn folder_validity(path: impl AsRef) -> Result {
/// Mask for the sticky bit
/// Taken from: http://man7.org/linux/man-pages/man7/inode.7.html
const S_ISVTX: u32 = 0x1000;
let path = path.as_ref();
let metadata = path.symlink_metadata().map_err(|e| (path.to_owned(), e))?;
if metadata.file_type().is_symlink() {
return Ok(TrashValidity::InvalidSymlink);
}
let mode = metadata.permissions().mode();
let no_sticky_bit = (mode & S_ISVTX) == 0;
if no_sticky_bit {
return Ok(TrashValidity::InvalidNotSticky);
}
Ok(TrashValidity::Valid)
}
/// Corresponds to the definition of "home_trash" from
/// https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
fn home_trash() -> Result {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
if data_home.len() > 0 {
let data_home_path = AsRef::::as_ref(data_home.as_os_str());
return Ok(data_home_path.join("Trash"));
}
}
if let Some(home) = std::env::var_os("HOME") {
if home.len() > 0 {
let home_path = AsRef::::as_ref(home.as_os_str());
return Ok(home_path.join(".local/share/Trash"));
}
}
Err(Error::Unknown { description: "Neither the XDG_DATA_HOME nor the HOME environment variable was found".into() })
}
fn home_topdir(mnt_points: &[MountPoint]) -> Result {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
if data_home.len() > 0 {
let data_home_path = AsRef::::as_ref(data_home.as_os_str());
return Ok(get_first_topdir_containing_path(data_home_path, mnt_points).to_owned());
}
}
if let Some(home) = std::env::var_os("HOME") {
if home.len() > 0 {
let home_path = AsRef::::as_ref(home.as_os_str());
return Ok(get_first_topdir_containing_path(home_path, mnt_points).to_owned());
}
}
Err(Error::Unknown { description: "Neither the XDG_DATA_HOME nor the HOME environment variable was found".into() })
}
fn get_first_topdir_containing_path<'a>(path: &Path, mnt_points: &'a [MountPoint]) -> &'a Path {
let root: &'static Path = Path::new("/");
mnt_points.iter().map(|mp| mp.mnt_dir.as_path()).find(|mount_path| path.starts_with(mount_path)).unwrap_or(root)
}
struct MountPoint {
mnt_dir: PathBuf,
_mnt_type: String,
_mnt_fsname: String,
}
/// Sorted by longest path first
fn get_sorted_mount_points() -> Result, Error> {
let mut mount_points = get_mount_points()?;
mount_points.sort_unstable_by(|a, b| {
let a = a.mnt_dir.as_os_str().as_bytes().len();
let b = b.mnt_dir.as_os_str().as_bytes().len();
a.cmp(&b).reverse()
});
Ok(mount_points)
}
#[cfg(target_os = "linux")]
fn get_mount_points() -> Result, Error> {
use once_cell::sync::Lazy;
use scopeguard::defer;
use std::ffi::{CStr, CString};
use std::sync::Mutex;
// The getmntinfo() function writes the array of structures to an internal
// static object and returns a pointer to that object. Subsequent calls to
// getmntent() will modify the same object. This means that the function is
// not threadsafe. To help prevent multiple threads using it concurrently
// via get_mount_points a Mutex is used.
// We understand that threads can still call `libc::getmntent(…)` directly
// to bypass the lock and trigger UB.
static LOCK: Lazy> = Lazy::new(|| Mutex::new(()));
let _lock = LOCK.lock().unwrap();
//let file;
let read_arg = CString::new("r").unwrap();
let mounts_path = CString::new("/proc/mounts").unwrap();
let mut file = unsafe { libc::fopen(mounts_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
if file.is_null() {
let mtab_path = CString::new("/etc/mtab").unwrap();
file = unsafe { libc::fopen(mtab_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
}
if file.is_null() {
return Err(Error::Unknown { description: "Neither '/proc/mounts' nor '/etc/mtab' could be opened.".into() });
}
defer! { unsafe { libc::fclose(file); } }
let mut result = Vec::new();
loop {
let mntent = unsafe { libc::getmntent(file) };
if mntent.is_null() {
break;
}
let dir = unsafe { CStr::from_ptr((*mntent).mnt_dir).to_str().unwrap() };
if dir.bytes().len() == 0 {
continue;
}
let mount_point = unsafe {
MountPoint {
mnt_dir: dir.into(),
_mnt_fsname: CStr::from_ptr((*mntent).mnt_fsname).to_str().unwrap().into(),
_mnt_type: CStr::from_ptr((*mntent).mnt_type).to_str().unwrap().into(),
}
};
result.push(mount_point);
}
if result.is_empty() {
return Err(Error::Unknown {
description: "A mount points file could be opened, but the call to `getmntent` returned NULL.".into(),
});
}
Ok(result)
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
fn get_mount_points() -> Result, Error> {
use once_cell::sync::Lazy;
use std::sync::Mutex;
// The getmntinfo() function writes the array of structures to an internal
// static object and returns a pointer to that object. Subsequent calls to
// getmntinfo() will modify the same object. This means that the function is
// not threadsafe. To help prevent multiple threads using it concurrently
// via get_mount_points a Mutex is used.
// We understand that threads can still call `libc::getmntinfo(…)` directly
// to bypass the lock and trigger UB.
static LOCK: Lazy> = Lazy::new(|| Mutex::new(()));
let _lock = LOCK.lock().unwrap();
fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> {
let buf: &[u8] = unsafe { std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()) };
if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to omit the null bytes
std::str::from_utf8(&buf[..pos]).ok()
} else {
std::str::from_utf8(buf).ok()
}
}
let mut fs_infos: *mut libc::statfs = std::ptr::null_mut();
let count = unsafe { libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT) };
if count < 1 {
return Ok(Vec::new());
}
let fs_infos: &[libc::statfs] = unsafe { std::slice::from_raw_parts(fs_infos as _, count as _) };
let mut result = Vec::new();
for fs_info in fs_infos {
if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 {
// If we have missing information, no need to look any further...
continue;
}
let fs_type = c_buf_to_str(&fs_info.f_fstypename).unwrap_or_default();
let mount_to = match c_buf_to_str(&fs_info.f_mntonname) {
Some(m) => m,
None => {
debug!("Cannot get disk mount point, ignoring it.");
continue;
}
};
let mount_from = c_buf_to_str(&fs_info.f_mntfromname).unwrap_or_default();
let mount_point =
MountPoint { mnt_dir: mount_to.into(), _mnt_fsname: mount_from.into(), _mnt_type: fs_type.into() };
result.push(mount_point);
}
Ok(result)
}
#[cfg(target_os = "netbsd")]
fn get_mount_points() -> Result, Error> {
use once_cell::sync::Lazy;
use std::sync::Mutex;
// The getmntinfo() function writes the array of structures to an internal
// static object and returns a pointer to that object. Subsequent calls to
// getmntinfo() will modify the same object. This means that the function is
// not threadsafe. To help prevent multiple threads using it concurrently
// via get_mount_points a Mutex is used.
// We understand that threads can still call `libc::getmntinfo(…)` directly
// to bypass the lock and trigger UB.
// NetBSD does not support statfs since 2005, so we need to use statvfs instead.
static LOCK: Lazy> = Lazy::new(|| Mutex::new(()));
let _lock = LOCK.lock().unwrap();
fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> {
let buf: &[u8] = unsafe { std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()) };
if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to omit the null bytes
std::str::from_utf8(&buf[..pos]).ok()
} else {
std::str::from_utf8(buf).ok()
}
}
let mut fs_infos: *mut libc::statvfs = std::ptr::null_mut();
let count = unsafe { libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT) };
if count < 1 {
return Ok(Vec::new());
}
let fs_infos: &[libc::statvfs] = unsafe { std::slice::from_raw_parts(fs_infos as _, count as _) };
let mut result = Vec::new();
for fs_info in fs_infos {
if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 {
// If we have missing information, no need to look any further...
continue;
}
let fs_type = c_buf_to_str(&fs_info.f_fstypename).unwrap_or_default();
let mount_to = match c_buf_to_str(&fs_info.f_mntonname) {
Some(m) => m,
None => {
debug!("Cannot get disk mount point, ignoring it.");
continue;
}
};
let mount_from = c_buf_to_str(&fs_info.f_mntfromname).unwrap_or_default();
let mount_point =
MountPoint { mnt_dir: mount_to.into(), _mnt_fsname: mount_from.into(), _mnt_type: fs_type.into() };
result.push(mount_point);
}
Ok(result)
}
#[cfg(not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
)))]
fn get_mount_points() -> Result, Error> {
// On platforms that don't have support yet, return an error
Err(Error::Unknown { description: "Mount points cannot be determined on this operating system".into() })
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use std::{
collections::{hash_map::Entry, HashMap},
env,
ffi::{OsStr, OsString},
fmt,
fs::File,
os::unix::{self, ffi::OsStringExt},
path::{Path, PathBuf},
process::Command,
};
use log::warn;
use crate::{
canonicalize_paths, delete, delete_all,
os_limited::{list, purge_all, restore_all},
platform::encode_uri_path,
tests::get_unique_name,
Error,
};
use super::decode_uri_path;
#[test]
#[serial]
fn test_list() {
crate::tests::init_logging();
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec = (0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i).into()).collect();
for _ in 0..batches {
for path in &names {
File::create(path).unwrap();
}
// eprintln!("Deleting {:?}", names);
let result = delete_all_using_system_program(&names);
if let Err(SystemTrashError::NoTrashProgram) = &result {
// For example may be the case on build systems that don't have a destop environment
warn!("No system default trashing utility was found, using this crate's implementation");
delete_all(&names).unwrap();
} else {
result.unwrap();
}
}
let items = list().unwrap();
let items: HashMap<_, Vec<_>> = items
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.fold(HashMap::new(), |mut map, x| {
match map.entry(x.name.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(x);
}
Entry::Vacant(entry) => {
entry.insert(vec![x]);
}
}
map
});
for name in names {
match items.get(&name) {
Some(items) => assert_eq!(items.len(), batches),
None => panic!("ERROR Could not find '{:?}' in {:#?}", name, items),
}
}
// Let's try to purge all the items we just created but ignore any errors
// as this test should succeed as long as `list` works properly.
let _ = purge_all(items.into_values().flatten());
}
#[test]
#[serial]
fn test_broken_symlinks() {
crate::tests::init_logging();
let file_name_prefix = get_unique_name();
let file_count = 2;
let names: Vec = (0..file_count).map(|i| format!("{}#{}", file_name_prefix, i).into()).collect();
let symlink_names: Vec = names.iter().map(|name| format!("{:?}-symlink", name).into()).collect();
// Test file symbolic link and directory symbolic link
File::create(&names[0]).unwrap();
std::fs::create_dir(&names[1]).unwrap();
for (i, (name, symlink)) in names.iter().zip(&symlink_names).enumerate() {
// Create symbolic link
unix::fs::symlink(name, symlink).unwrap();
// Break the symbolic link
if i == 0 {
std::fs::remove_file(name).unwrap();
} else {
std::fs::remove_dir(name).unwrap();
}
// Delete and Restore it without errors
delete(symlink).unwrap();
let items = list().unwrap();
let item = items.into_iter().find(|it| it.name == *symlink).unwrap();
restore_all([item.clone()]).expect("The broken symbolic link should be restored successfully.");
// Delete and Purge it without errors
delete(symlink).unwrap();
purge_all([item]).expect("The broken symbolic link should be purged successfully.");
}
}
#[test]
fn uri_enc_dec_roundtrip() {
let fake = format!("/tmp/{}", get_unique_name());
let path = decode_uri_path(&fake);
assert_eq!(path.to_str().expect("Path is valid Unicode"), fake, "Decoded path shouldn't be different");
let encoded = encode_uri_path(&path);
assert_eq!(encoded, fake, "URL encoded alphanumeric String shouldn't change");
}
#[test]
fn uri_enc_dec_roundtrip_invalid_unicode() {
let base = OsStr::new(&format!("/tmp/{}/", get_unique_name())).to_os_string();
// Add invalid UTF-8 byte
let mut bytes = base.into_encoded_bytes();
bytes.push(168);
let fake = OsString::from_vec(bytes);
assert!(fake.to_str().is_none(), "Invalid Unicode cannot be a Rust String");
let path = decode_uri_path(&fake);
assert_eq!(path.as_os_str().as_encoded_bytes(), fake.as_encoded_bytes());
// Shouldn't panic
encode_uri_path(&path);
}
//////////////////////////////////////////////////////////////////////////////////////
/// System
//////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub enum SystemTrashError {
NoTrashProgram,
Other(Error),
}
impl fmt::Display for SystemTrashError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SystemTrashError during a `trash` operation: {:?}", self)
}
}
impl std::error::Error for SystemTrashError {}
fn is_program_in_path(program: &str) -> bool {
if let Some(path_vars) = std::env::var_os("PATH") {
for path in std::env::split_paths(&path_vars) {
let full_path = path.join(program);
if full_path.is_file() {
return true;
}
}
}
false
}
/// This is based on the electron library's implementation.
/// See: https://github.com/electron/electron/blob/34c4c8d5088fa183f56baea28809de6f2a427e02/shell/common/platform_util_linux.cc#L96
pub fn delete_all_canonicalized_using_system_program(full_paths: Vec) -> Result<(), SystemTrashError> {
static DEFAULT_TRASH: &str = "gio";
let trash = {
// Determine desktop environment and set accordingly.
let desktop_env = get_desktop_environment();
if desktop_env == DesktopEnvironment::Kde4 || desktop_env == DesktopEnvironment::Kde5 {
"kioclient5"
} else if desktop_env == DesktopEnvironment::Kde3 {
"kioclient"
} else {
DEFAULT_TRASH
}
};
let mut argv = Vec::::with_capacity(full_paths.len() + 2);
if trash == "kioclient5" || trash == "kioclient" {
//argv.push(trash.into());
argv.push("move".into());
for full_path in &full_paths {
argv.push(full_path.into());
}
argv.push("trash:/".into());
} else {
//argv.push_back(ELECTRON_DEFAULT_TRASH);
argv.push("trash".into());
for full_path in &full_paths {
argv.push(full_path.into());
}
}
if !is_program_in_path(trash) {
return Err(SystemTrashError::NoTrashProgram);
}
// Execute command
let mut command = Command::new(trash);
command.args(argv);
let result = command.output().map_err(|e| {
SystemTrashError::Other(Error::Unknown {
description: format!("Tried executing: {:?} - Error was: {}", command, e),
})
})?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
return Err(SystemTrashError::Other(Error::Unknown {
description: format!("Used '{}', stderr: {}", trash, stderr),
}));
}
Ok(())
}
pub fn delete_all_using_system_program(paths: I) -> Result<(), SystemTrashError>
where
I: IntoIterator
- ,
T: AsRef,
{
let full_paths = canonicalize_paths(paths).map_err(SystemTrashError::Other)?;
delete_all_canonicalized_using_system_program(full_paths)
}
#[derive(PartialEq)]
enum DesktopEnvironment {
Other,
Cinnamon,
Gnome,
// KDE3, KDE4 and KDE5 are sufficiently different that we count
// them as different desktop environments here.
Kde3,
Kde4,
Kde5,
Pantheon,
Unity,
Xfce,
}
fn env_has_var(name: &str) -> bool {
env::var_os(name).is_some()
}
/// See: https://chromium.googlesource.com/chromium/src/+/dd407d416fa941c04e33d81f2b1d8cab8196b633/base/nix/xdg_util.cc#57
fn get_desktop_environment() -> DesktopEnvironment {
static KDE_SESSION_ENV_VAR: &str = "KDE_SESSION_VERSION";
// XDG_CURRENT_DESKTOP is the newest standard circa 2012.
if let Ok(xdg_current_desktop) = env::var("XDG_CURRENT_DESKTOP") {
// It could have multiple values separated by colon in priority order.
for value in xdg_current_desktop.split(':') {
let value = value.trim();
if value.is_empty() {
continue;
}
match value {
"Unity" => {
// gnome-fallback sessions set XDG_CURRENT_DESKTOP to Unity
// DESKTOP_SESSION can be gnome-fallback or gnome-fallback-compiz
if let Ok(desktop_session) = env::var("DESKTOP_SESSION") {
if desktop_session.contains("gnome-fallback") {
return DesktopEnvironment::Gnome;
}
}
return DesktopEnvironment::Unity;
}
"GNOME" => {
return DesktopEnvironment::Gnome;
}
"X-Cinnamon" => {
return DesktopEnvironment::Cinnamon;
}
"KDE" => {
if let Ok(kde_session) = env::var(KDE_SESSION_ENV_VAR) {
if kde_session == "5" {
return DesktopEnvironment::Kde5;
}
}
return DesktopEnvironment::Kde4;
}
"Pantheon" => {
return DesktopEnvironment::Pantheon;
}
"XFCE" => {
return DesktopEnvironment::Xfce;
}
_ => {}
}
}
}
// DESKTOP_SESSION was what everyone used in 2010.
if let Ok(desktop_session) = env::var("DESKTOP_SESSION") {
match desktop_session.as_str() {
"gnome" | "mate" => {
return DesktopEnvironment::Gnome;
}
"kde4" | "kde-plasma" => {
return DesktopEnvironment::Kde4;
}
"kde" => {
// This may mean KDE4 on newer systems, so we have to check.
if env_has_var(KDE_SESSION_ENV_VAR) {
return DesktopEnvironment::Kde4;
}
return DesktopEnvironment::Kde3;
}
"xubuntu" => {
return DesktopEnvironment::Xfce;
}
_ => {}
}
if desktop_session.contains("xfce") {
return DesktopEnvironment::Xfce;
}
}
// Fall back on some older environment variables.
// Useful particularly in the DESKTOP_SESSION=default case.
if env_has_var("GNOME_DESKTOP_SESSION_ID") {
return DesktopEnvironment::Gnome;
} else if env_has_var("KDE_FULL_SESSION") {
if env_has_var(KDE_SESSION_ENV_VAR) {
return DesktopEnvironment::Kde4;
}
return DesktopEnvironment::Kde3;
}
DesktopEnvironment::Other
}
}
fn fs_error(path: impl Into, source: std::io::Error) -> Error {
Error::FileSystem { path: path.into(), source }
}
trash-5.2.0/src/lib.rs 0000644 0000000 0000000 00000044646 10461020230 0012672 0 ustar 0000000 0000000 //! This crate provides functions that allow moving files to the operating system's Recycle Bin or
//! Trash, or the equivalent.
//!
//! Furthermore on Linux and on Windows additional functions are available from the `os_limited`
//! module.
//!
//! ### Potential UB on Linux and FreeBSD
//!
//! When querying information about mount points, non-threadsafe versions of `libc::getmnt(info|ent)` are
//! used which can cause UB if another thread calls into the same function, _probably_ only if the mountpoints
//! changed as well.
//!
//! To neutralize the issue, the respective function in this crate has been made thread-safe with a Mutex.
//!
//! **If your crate calls into the aforementioned methods directly or indirectly from other threads,
//! rather not use this crate.**
//!
//! As the handling of UB is clearly a trade-off and certainly goes against the zero-chance-of-UB goal
//! of the Rust community, please interact with us [in the tracking issue](https://github.com/Byron/trash-rs/issues/42)
//! to help find a more permanent solution.
//!
//! ### Notes on the Linux implementation
//!
//! This library implements version 1.0 of the [Freedesktop.org
//! Trash](https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html) specification and
//! aims to match the behaviour of Ubuntu 18.04 GNOME in cases of ambiguity. Most -if not all- Linux
//! distributions that ship with a desktop environment follow this specification. For example
//! GNOME, KDE, and XFCE all use this convention. This crate blindly assumes that the Linux
//! distribution it runs on, follows this specification.
//!
use std::ffi::OsString;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::fmt;
use std::{env::current_dir, error};
use log::trace;
#[cfg(test)]
pub mod tests;
#[cfg(target_os = "windows")]
#[path = "windows.rs"]
mod platform;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
#[path = "freedesktop.rs"]
mod platform;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "macos")]
use macos as platform;
pub const DEFAULT_TRASH_CTX: TrashContext = TrashContext::new();
/// A collection of preferences for trash operations.
#[derive(Clone, Default, Debug)]
pub struct TrashContext {
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
platform_specific: platform::PlatformTrashContext,
}
impl TrashContext {
pub const fn new() -> Self {
Self { platform_specific: platform::PlatformTrashContext::new() }
}
/// Removes a single file or directory.
///
/// When a symbolic link is provided to this function, the symbolic link will be removed and the link
/// target will be kept intact.
///
/// # Example
///
/// ```
/// use std::fs::File;
/// use trash::delete;
/// File::create("delete_me").unwrap();
/// trash::delete("delete_me").unwrap();
/// assert!(File::open("delete_me").is_err());
/// ```
pub fn delete>(&self, path: T) -> Result<(), Error> {
self.delete_all(&[path])
}
/// Removes all files/directories specified by the collection of paths provided as an argument.
///
/// When a symbolic link is provided to this function, the symbolic link will be removed and the link
/// target will be kept intact.
///
/// # Example
///
/// ```
/// use std::fs::File;
/// use trash::delete_all;
/// File::create("delete_me_1").unwrap();
/// File::create("delete_me_2").unwrap();
/// delete_all(&["delete_me_1", "delete_me_2"]).unwrap();
/// assert!(File::open("delete_me_1").is_err());
/// assert!(File::open("delete_me_2").is_err());
/// ```
pub fn delete_all(&self, paths: I) -> Result<(), Error>
where
I: IntoIterator
- ,
T: AsRef,
{
trace!("Starting canonicalize_paths");
let full_paths = canonicalize_paths(paths)?;
trace!("Finished canonicalize_paths");
self.delete_all_canonicalized(full_paths)
}
}
/// Convenience method for `DEFAULT_TRASH_CTX.delete()`.
///
/// See: [`TrashContext::delete`](TrashContext::delete)
pub fn delete>(path: T) -> Result<(), Error> {
DEFAULT_TRASH_CTX.delete(path)
}
/// Convenience method for `DEFAULT_TRASH_CTX.delete_all()`.
///
/// See: [`TrashContext::delete_all`](TrashContext::delete_all)
pub fn delete_all(paths: I) -> Result<(), Error>
where
I: IntoIterator
- ,
T: AsRef,
{
DEFAULT_TRASH_CTX.delete_all(paths)
}
/// Provides information about an error.
#[derive(Debug)]
pub enum Error {
Unknown {
description: String,
},
Os {
code: i32,
description: String,
},
/// **freedesktop only**
///
/// Error coming from file system
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
FileSystem {
path: PathBuf,
source: std::io::Error,
},
/// One of the target items was a root folder.
/// If a list of items are requested to be removed by a single function call (e.g. `delete_all`)
/// and this error is returned, then it's guaranteed that none of the items is removed.
TargetedRoot,
/// The `target` does not exist or the process has insufficient permissions to access it.
CouldNotAccess {
target: String,
},
/// Error while canonicalizing path.
CanonicalizePath {
/// Path that triggered the error.
original: PathBuf,
},
/// Error while converting an [`OsString`] to a [`String`].
///
/// This may also happen when converting a [`Path`] or [`PathBuf`] to an [`OsString`].
ConvertOsString {
/// The string that was attempted to be converted.
original: OsString,
},
/// This kind of error happens when a trash item's original parent already contains an item with
/// the same name and type (file or folder). In this case an error is produced and the
/// restoration of the files is halted meaning that there may be files that could be restored
/// but were left in the trash due to the error.
///
/// One should not assume any relationship between the order that the items were supplied and
/// the list of remaining items. That is to say, it may be that the item that collided was in
/// the middle of the provided list but the remaining items' list contains all the provided
/// items.
///
/// `path`: The path of the file that's blocking the trash item from being restored.
///
/// `remaining_items`: All items that were not restored in the order they were provided,
/// starting with the item that triggered the error.
RestoreCollision {
path: PathBuf,
remaining_items: Vec,
},
/// This sort of error is returned when multiple items with the same `original_path` were
/// requested to be restored. These items are referred to as twins here. If there are twins
/// among the items, then none of the items are restored.
///
/// `path`: The `original_path` of the twins.
///
/// `items`: The complete list of items that were handed over to the `restore_all` function.
RestoreTwins {
path: PathBuf,
items: Vec,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error during a `trash` operation: {self:?}")
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
Self::FileSystem { path: _, source: e } => e.source(),
_ => None,
}
}
}
pub fn into_unknown(err: E) -> Error {
Error::Unknown { description: format!("{err}") }
}
pub(crate) fn canonicalize_paths(paths: I) -> Result, Error>
where
I: IntoIterator
- ,
T: AsRef,
{
let paths = paths.into_iter();
paths
.map(|x| {
let target_ref = x.as_ref();
if target_ref.as_os_str().is_empty() {
return Err(Error::CanonicalizePath { original: target_ref.to_owned() });
}
let target = if target_ref.is_relative() {
let curr_dir = current_dir()
.map_err(|_| Error::CouldNotAccess { target: "[Current working directory]".into() })?;
curr_dir.join(target_ref)
} else {
target_ref.to_owned()
};
let parent = target.parent().ok_or(Error::TargetedRoot)?;
let canonical_parent =
parent.canonicalize().map_err(|_| Error::CanonicalizePath { original: parent.to_owned() })?;
if let Some(file_name) = target.file_name() {
Ok(canonical_parent.join(file_name))
} else {
// `file_name` is none if the path ends with `..`
Ok(canonical_parent)
}
})
.collect::, _>>()
}
/// This struct holds information about a single item within the trash.
///
/// A trash item can be a file or folder or any other object that the target
/// operating system allows to put into the trash.
#[derive(Debug, Clone)]
pub struct TrashItem {
/// A system specific identifier of the item in the trash.
///
/// On Windows it is the string returned by `IShellItem::GetDisplayName`
/// with the `SIGDN_DESKTOPABSOLUTEPARSING` flag.
///
/// On Linux it is an absolute path to the `.trashinfo` file associated with
/// the item.
pub id: OsString,
/// The name of the item. For example if the folder '/home/user/New Folder'
/// was deleted, its `name` is 'New Folder'
pub name: OsString,
/// The path to the parent folder of this item before it was put inside the
/// trash. For example if the folder '/home/user/New Folder' is in the
/// trash, its `original_parent` is '/home/user'.
///
/// To get the full path to the file in its original location use the
/// `original_path` function.
pub original_parent: PathBuf,
/// The number of non-leap seconds elapsed between the UNIX Epoch and the
/// moment the file was deleted.
/// Without the "chrono" feature, this will be a negative number on linux only.
pub time_deleted: i64,
}
impl TrashItem {
/// Joins the `original_parent` and `name` fields to obtain the full path to
/// the original file.
pub fn original_path(&self) -> PathBuf {
self.original_parent.join(&self.name)
}
}
impl PartialEq for TrashItem {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for TrashItem {}
impl Hash for TrashItem {
fn hash(&self, state: &mut H) {
self.id.hash(state);
}
}
/// Size of a [`TrashItem`] in bytes or entries
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum TrashItemSize {
/// Number of bytes in a file
Bytes(u64),
/// Number of entries in a directory, non-recursive
Entries(usize),
}
impl TrashItemSize {
/// The size of a file in bytes, if this item is a file.
pub fn size(&self) -> Option {
match self {
TrashItemSize::Bytes(s) => Some(*s),
TrashItemSize::Entries(_) => None,
}
}
/// The amount of entries in the directory, if this is a directory.
pub fn entries(&self) -> Option {
match self {
TrashItemSize::Bytes(_) => None,
TrashItemSize::Entries(e) => Some(*e),
}
}
}
/// Metadata about a [`TrashItem`]
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct TrashItemMetadata {
/// The size of the item, depending on whether or not it is a directory.
pub size: TrashItemSize,
}
#[cfg(any(
target_os = "windows",
all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android"))
))]
pub mod os_limited {
//! This module provides functionality which is only supported on Windows and
//! Linux or other Freedesktop Trash compliant environment.
use std::{
borrow::Borrow,
collections::HashSet,
hash::{Hash, Hasher},
};
use super::{platform, Error, TrashItem, TrashItemMetadata};
/// Returns all [`TrashItem`]s that are currently in the trash.
///
/// The items are in no particular order and must be sorted when any kind of ordering is required.
///
/// # Example
///
/// ```
/// use trash::os_limited::list;
/// let trash_items = list().unwrap();
/// println!("{:#?}", trash_items);
/// ```
pub fn list() -> Result, Error> {
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 {
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
/// bins across mount points. Some, or all of these, may not exist or be invalid in some way.
///
/// # Example
///
/// ```
/// # #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] {
/// use trash::os_limited::trash_folders;
/// let trash_bins = trash_folders()?;
/// println!("{trash_bins:#?}");
/// # }
/// # Ok::<(), trash::Error>(())
/// ```
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn trash_folders() -> Result, Error> {
platform::trash_folders()
}
/// Returns the [`TrashItemMetadata`] for a [`TrashItem`]
///
/// # Example
///
/// ```
/// use trash::os_limited::{list, metadata};
/// let trash_items = list().unwrap();
/// for item in trash_items {
/// println!("{:#?}", metadata(&item).unwrap());
/// }
/// ```
pub fn metadata(item: &TrashItem) -> Result {
platform::metadata(item)
}
/// Deletes all the provided [`TrashItem`]s permanently.
///
/// This function consumes the provided items.
///
/// # Example
///
/// Taking items' ownership:
///
/// ```
/// use std::fs::File;
/// use trash::{delete, os_limited::{list, purge_all}};
///
/// let filename = "trash-purge_all-example-ownership";
/// File::create(filename).unwrap();
/// delete(filename).unwrap();
/// // Collect the filtered list just so that we can make sure there's exactly one element.
/// // There's no need to `collect` it otherwise.
/// let selected: Vec<_> = list().unwrap().into_iter().filter(|x| x.name == filename).collect();
/// assert_eq!(selected.len(), 1);
/// purge_all(selected).unwrap();
/// ```
///
/// Taking items' reference:
///
/// ```
/// use std::fs::File;
/// use trash::{delete, os_limited::{list, purge_all}};
///
/// let filename = "trash-purge_all-example-reference";
/// File::create(filename).unwrap();
/// delete(filename).unwrap();
/// let mut selected = list().unwrap();
/// selected.retain(|x| x.name == filename);
/// assert_eq!(selected.len(), 1);
/// purge_all(&selected).unwrap();
/// ```
pub fn purge_all(items: I) -> Result<(), Error>
where
I: IntoIterator,
::Item: Borrow,
{
platform::purge_all(items)
}
/// Restores all the provided [`TrashItem`] to their original location.
///
/// This function consumes the provided items.
///
/// # Errors
///
/// Errors this function may return include but are not limited to the following.
///
/// It may be the case that when restoring a file or a folder, the `original_path` already has
/// a new item with the same name. When such a collision happens this function returns a
/// [`RestoreCollision`] kind of error.
///
/// If two or more of the provided items have identical `original_path`s then a
/// [`RestoreTwins`] kind of error is returned.
///
/// # Example
///
/// Basic usage:
///
/// ```
/// use std::fs::File;
/// use trash::os_limited::{list, restore_all};
///
/// let filename = "trash-restore_all-example";
/// File::create(filename).unwrap();
/// restore_all(list().unwrap().into_iter().filter(|x| x.name == filename)).unwrap();
/// std::fs::remove_file(filename).unwrap();
/// ```
///
/// Retry restoring when encountering [`RestoreCollision`] error:
///
/// ```no_run
/// use trash::os_limited::{list, restore_all};
/// use trash::Error::RestoreCollision;
///
/// let items = list().unwrap();
/// if let Err(RestoreCollision { path, mut remaining_items }) = restore_all(items) {
/// // keep all except the one(s) that couldn't be restored
/// remaining_items.retain(|e| e.original_path() != path);
/// restore_all(remaining_items).unwrap();
/// }
/// ```
///
/// [`RestoreCollision`]: Error::RestoreCollision
/// [`RestoreTwins`]: Error::RestoreTwins
pub fn restore_all(items: I) -> Result<(), Error>
where
I: IntoIterator
- ,
{
// Check for twins here cause that's pretty platform independent.
struct ItemWrapper<'a>(&'a TrashItem);
impl<'a> PartialEq for ItemWrapper<'a> {
fn eq(&self, other: &Self) -> bool {
self.0.original_path() == other.0.original_path()
}
}
impl<'a> Eq for ItemWrapper<'a> {}
impl<'a> Hash for ItemWrapper<'a> {
fn hash(&self, state: &mut H) {
self.0.original_path().hash(state);
}
}
let items = items.into_iter().collect::>();
let mut item_set = HashSet::with_capacity(items.len());
for item in items.iter() {
if !item_set.insert(ItemWrapper(item)) {
return Err(Error::RestoreTwins { path: item.original_path(), items });
}
}
platform::restore_all(items)
}
}
trash-5.2.0/src/macos.rs 0000644 0000000 0000000 00000012641 10461020230 0013214 0 ustar 0000000 0000000 use std::{ffi::OsString, path::PathBuf, process::Command};
use log::trace;
use objc2_foundation::{NSFileManager, NSString, NSURL};
use crate::{into_unknown, Error, TrashContext};
#[derive(Copy, Clone, Debug)]
pub enum DeleteMethod {
/// Use an `osascript`, asking the Finder application to delete the files.
///
/// - Might ask the user to give additional permissions to the app
/// - Produces the sound that Finder usually makes when deleting a file
/// - Shows the "Put Back" option in the context menu, when using the Finder application
///
/// This is the default.
Finder,
/// Use `trashItemAtURL` from the `NSFileManager` object to delete the files.
///
/// - Somewhat faster than the `Finder` method
/// - Does *not* require additional permissions
/// - Does *not* produce the sound that Finder usually makes when deleting a file
/// - Does *not* show the "Put Back" option on some systems (the file may be restored by for
/// example dragging out from the Trash folder). This is a macOS bug. Read more about it
/// at:
/// -
/// -
NsFileManager,
}
impl DeleteMethod {
/// Returns `DeleteMethod::Finder`
pub const fn new() -> Self {
DeleteMethod::Finder
}
}
impl Default for DeleteMethod {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Default, Debug)]
pub struct PlatformTrashContext {
delete_method: DeleteMethod,
}
impl PlatformTrashContext {
pub const fn new() -> Self {
Self { delete_method: DeleteMethod::new() }
}
}
pub trait TrashContextExtMacos {
fn set_delete_method(&mut self, method: DeleteMethod);
fn delete_method(&self) -> DeleteMethod;
}
impl TrashContextExtMacos for TrashContext {
fn set_delete_method(&mut self, method: DeleteMethod) {
self.platform_specific.delete_method = method;
}
fn delete_method(&self) -> DeleteMethod {
self.platform_specific.delete_method
}
}
impl TrashContext {
pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec) -> Result<(), Error> {
let full_paths = full_paths.into_iter().map(to_string).collect::, _>>()?;
match self.platform_specific.delete_method {
DeleteMethod::Finder => delete_using_finder(full_paths),
DeleteMethod::NsFileManager => delete_using_file_mgr(full_paths),
}
}
}
fn delete_using_file_mgr(full_paths: Vec) -> Result<(), Error> {
trace!("Starting delete_using_file_mgr");
let file_mgr = unsafe { NSFileManager::defaultManager() };
for path in full_paths {
let string = NSString::from_str(&path);
trace!("Starting fileURLWithPath");
let url = unsafe { NSURL::fileURLWithPath(&string) };
trace!("Finished fileURLWithPath");
trace!("Calling trashItemAtURL");
let res = unsafe { file_mgr.trashItemAtURL_resultingItemURL_error(&url, None) };
trace!("Finished trashItemAtURL");
if let Err(err) = res {
return Err(Error::Unknown {
description: format!("While deleting '{path}', `trashItemAtURL` failed: {err}"),
});
}
}
Ok(())
}
fn delete_using_finder(full_paths: Vec) -> Result<(), Error> {
// AppleScript command to move files (or directories) to Trash looks like
// osascript -e 'tell application "Finder" to delete { POSIX file "file1", POSIX "file2" }'
// The `-e` flag is used to execute only one line of AppleScript.
let mut command = Command::new("osascript");
let posix_files = full_paths.into_iter().map(|p| format!("POSIX file \"{p}\"")).collect::>().join(", ");
let script = format!("tell application \"Finder\" to delete {{ {posix_files} }}");
let argv: Vec = vec!["-e".into(), script.into()];
command.args(argv);
// Execute command
let result = command.output().map_err(into_unknown)?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
match result.status.code() {
None => {
return Err(Error::Unknown {
description: format!("The AppleScript exited with error. stderr: {}", stderr),
})
}
Some(code) => {
return Err(Error::Os {
code,
description: format!("The AppleScript exited with error. stderr: {}", stderr),
})
}
};
}
Ok(())
}
fn to_string>(str_in: T) -> Result {
let os_string = str_in.into();
let s = os_string.to_str();
match s {
Some(s) => Ok(s.to_owned()),
None => Err(Error::ConvertOsString { original: os_string }),
}
}
#[cfg(test)]
mod tests {
use crate::{
macos::{DeleteMethod, TrashContextExtMacos},
tests::{get_unique_name, init_logging},
TrashContext,
};
use serial_test::serial;
use std::fs::File;
#[test]
#[serial]
fn test_delete_with_ns_file_manager() {
init_logging();
let mut trash_ctx = TrashContext::default();
trash_ctx.set_delete_method(DeleteMethod::NsFileManager);
let path = get_unique_name();
File::create(&path).unwrap();
trash_ctx.delete(&path).unwrap();
assert!(File::open(&path).is_err());
}
}
trash-5.2.0/src/tests.rs 0000644 0000000 0000000 00000026376 10461020230 0013266 0 ustar 0000000 0000000 mod utils {
use std::sync::atomic::{AtomicI32, Ordering};
use once_cell::sync::Lazy;
// WARNING Expecting that `cargo test` won't be invoked on the same computer more than once within
// a single millisecond
static INSTANCE_ID: Lazy = Lazy::new(|| chrono::Local::now().timestamp_millis());
static ID_OFFSET: AtomicI32 = AtomicI32::new(0);
pub fn get_unique_name() -> String {
let id = ID_OFFSET.fetch_add(1, Ordering::SeqCst);
format!("trash-test-{}-{}", *INSTANCE_ID, id)
}
pub fn init_logging() {
let _ = env_logger::builder().is_test(true).try_init();
}
}
pub use utils::{get_unique_name, init_logging};
#[cfg(any(
target_os = "windows",
all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android"))
))]
mod os_limited {
use super::{get_unique_name, init_logging};
use serial_test::serial;
use std::collections::{hash_map::Entry, HashMap};
use std::ffi::{OsStr, OsString};
use std::fs::File;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
use std::os::unix::ffi::OsStringExt;
use crate as trash;
#[test]
#[serial]
fn list() {
const MAX_SECONDS_DIFFERENCE: i64 = 10;
init_logging();
let deletion_time = chrono::Utc::now();
let actual_unix_deletion_time = deletion_time.naive_utc().timestamp();
assert_eq!(actual_unix_deletion_time, deletion_time.naive_local().timestamp());
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec = (0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i).into()).collect();
for _ in 0..batches {
for path in names.iter() {
File::create(path).unwrap();
}
trash::delete_all(&names).unwrap();
}
let items = trash::os_limited::list().unwrap();
let items: HashMap<_, Vec<_>> = items
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.fold(HashMap::new(), |mut map, x| {
match map.entry(x.name.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(x);
}
Entry::Vacant(entry) => {
entry.insert(vec![x]);
}
}
map
});
for name in names {
match items.get(&name) {
Some(items) => {
assert_eq!(items.len(), batches);
for item in items {
if cfg!(feature = "chrono") {
let diff = (item.time_deleted - actual_unix_deletion_time).abs();
if diff > MAX_SECONDS_DIFFERENCE {
panic!(
"The deleted item does not have the timestamp that represents its deletion time. Expected: {}. Got: {}",
actual_unix_deletion_time,
item.time_deleted
);
}
}
}
}
None => panic!("ERROR Could not find '{:?}' in {:#?}", name, items),
}
}
// Let's try to purge all the items we just created but ignore any errors
// as this test should succeed as long as `list` works properly.
let _ = trash::os_limited::purge_all(items.iter().flat_map(|(_name, item)| item));
}
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
#[test]
#[serial]
fn list_invalid_utf8() {
let mut name = OsStr::new(&get_unique_name()).to_os_string().into_encoded_bytes();
name.push(168);
let name = OsString::from_vec(name);
File::create(&name).unwrap();
// Delete, list, and remove file with an invalid UTF8 name
// Listing items is already exhaustively checked above, so this test is mainly concerned
// with checking that listing non-Unicode names does not panic
trash::delete(&name).unwrap();
let item = trash::os_limited::list().unwrap().into_iter().find(|item| item.name == name).unwrap();
let _ = trash::os_limited::purge_all([item]);
}
#[test]
fn purge_empty() {
init_logging();
trash::os_limited::purge_all::>(vec![]).unwrap();
}
#[test]
fn restore_empty() {
init_logging();
trash::os_limited::restore_all(vec![]).unwrap();
}
#[test]
#[serial]
fn purge() {
init_logging();
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec<_> = (0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for _ in 0..batches {
for path in names.iter() {
File::create(path).unwrap();
}
trash::delete_all(&names).unwrap();
}
// Collect it because we need the exact number of items gathered.
let targets: Vec<_> = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.collect();
assert_eq!(targets.len(), batches * files_per_batch);
trash::os_limited::purge_all(targets).unwrap();
let remaining = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.count();
assert_eq!(remaining, 0);
}
#[test]
#[serial]
fn restore() {
init_logging();
let file_name_prefix = get_unique_name();
let file_count: usize = 3;
let names: Vec<_> = (0..file_count).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for path in names.iter() {
File::create(path).unwrap();
}
trash::delete_all(&names).unwrap();
// Collect it because we need the exact number of items gathered.
let targets: Vec<_> = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.collect();
assert_eq!(targets.len(), file_count);
trash::os_limited::restore_all(targets).unwrap();
let remaining = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.count();
assert_eq!(remaining, 0);
// They are not in the trash anymore but they should be at their original location
let mut missing = Vec::new();
for path in names.iter() {
if !std::path::Path::new(&path).is_file() {
missing.push(path);
}
}
for path in names.iter() {
std::fs::remove_file(path).ok();
}
assert_eq!(missing, Vec::<&String>::new());
}
#[test]
#[serial]
fn restore_collision() {
init_logging();
let file_name_prefix = get_unique_name();
let file_count: usize = 3;
let collision_remaining = file_count - 1;
let names: Vec<_> = (0..file_count).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for path in names.iter() {
File::create(path).unwrap();
}
trash::delete_all(&names).unwrap();
for path in names.iter().skip(file_count - collision_remaining) {
File::create(path).unwrap();
}
let mut targets: Vec<_> = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.collect();
targets.sort_by(|a, b| a.name.cmp(&b.name));
assert_eq!(targets.len(), file_count);
let remaining_count = match trash::os_limited::restore_all(targets) {
Err(trash::Error::RestoreCollision { remaining_items, .. }) => {
let contains = |v: &Vec, name: &String| {
for curr in v.iter() {
if curr.name.as_encoded_bytes() == name.as_bytes() {
return true;
}
}
false
};
// Are all items that got restored reside in the folder?
for path in names.iter().filter(|filename| !contains(&remaining_items, filename)) {
assert!(File::open(path).is_ok());
}
remaining_items.len()
}
_ => {
for path in names.iter() {
std::fs::remove_file(path).ok();
}
panic!("restore_all was expected to return `trash::ErrorKind::RestoreCollision` but did not.");
}
};
let remaining = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.collect::>();
assert_eq!(remaining.len(), remaining_count);
trash::os_limited::purge_all(remaining).unwrap();
for path in names.iter() {
// This will obviously fail on the items that both didn't collide and weren't restored.
std::fs::remove_file(path).ok();
}
}
#[test]
#[serial]
fn restore_twins() {
init_logging();
let file_name_prefix = get_unique_name();
let file_count: usize = 4;
let names: Vec<_> = (0..file_count).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for path in names.iter() {
File::create(path).unwrap();
}
trash::delete_all(&names).unwrap();
let twin_name = &names[1];
File::create(twin_name).unwrap();
trash::delete(twin_name).unwrap();
let mut targets: Vec<_> = trash::os_limited::list()
.unwrap()
.into_iter()
.filter(|x| x.name.as_encoded_bytes().starts_with(file_name_prefix.as_bytes()))
.collect();
targets.sort_by(|a, b| a.name.cmp(&b.name));
assert_eq!(targets.len(), file_count + 1); // plus one for one of the twins
match trash::os_limited::restore_all(targets) {
Err(trash::Error::RestoreTwins { path, items }) => {
assert_eq!(path.file_name().unwrap().to_str().unwrap(), twin_name);
trash::os_limited::purge_all(items).unwrap();
}
_ => 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()");
}
}
trash-5.2.0/src/windows.rs 0000644 0000000 0000000 00000027665 10461020230 0013620 0 ustar 0000000 0000000 use crate::{Error, TrashContext, TrashItem, TrashItemMetadata, TrashItemSize};
use std::{
borrow::Borrow,
ffi::{c_void, OsStr, OsString},
os::windows::{ffi::OsStrExt, prelude::*},
path::PathBuf,
};
use windows::Win32::{
Foundation::*, Storage::EnhancedStorage::*, System::Com::*, System::SystemServices::*,
UI::Shell::PropertiesSystem::*, UI::Shell::*,
};
use windows::{
core::{Interface, PCWSTR, PWSTR},
Win32::System::Com::StructuredStorage::PropVariantToBSTR,
};
const SCID_ORIGINAL_LOCATION: PROPERTYKEY = PROPERTYKEY { fmtid: PSGUID_DISPLACED, pid: PID_DISPLACED_FROM };
const SCID_DATE_DELETED: PROPERTYKEY = PROPERTYKEY { fmtid: PSGUID_DISPLACED, pid: PID_DISPLACED_DATE };
impl From for Error {
fn from(err: windows::core::Error) -> Error {
Error::Os { code: err.code().0, description: format!("windows error: {err}") }
}
}
fn to_wide_path(path: impl AsRef) -> Vec {
path.as_ref().encode_wide().chain(std::iter::once(0)).collect()
}
#[derive(Clone, Default, Debug)]
pub struct PlatformTrashContext;
impl PlatformTrashContext {
pub const fn new() -> Self {
PlatformTrashContext
}
}
impl TrashContext {
/// See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-_shfileopstructa
pub(crate) fn delete_specified_canonicalized(&self, full_paths: Vec) -> Result<(), Error> {
ensure_com_initialized();
unsafe {
let pfo: IFileOperation = CoCreateInstance(&FileOperation as *const _, None, CLSCTX_ALL).unwrap();
pfo.SetOperationFlags(FOF_NO_UI | FOF_ALLOWUNDO | FOF_WANTNUKEWARNING)?;
for full_path in full_paths.iter() {
let path_prefix = ['\\' as u16, '\\' as u16, '?' as u16, '\\' as u16];
let wide_path_container = to_wide_path(full_path);
let wide_path_slice = if wide_path_container.starts_with(&path_prefix) {
&wide_path_container[path_prefix.len()..]
} else {
&wide_path_container[0..]
};
let shi: IShellItem = SHCreateItemFromParsingName(PCWSTR(wide_path_slice.as_ptr()), None)?;
pfo.DeleteItem(&shi, None)?;
}
pfo.PerformOperations()?;
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-performoperations
// this method can still return a success code. Use the GetAnyOperationsAborted method to determine if this was the case.
if pfo.GetAnyOperationsAborted()?.as_bool() {
// TODO: return the reason why the operation was aborted.
// We may retrieve reason from the IFileOperationProgressSink but
// the list of HRESULT codes is not documented.
return Err(Error::Unknown { description: "Some operations were aborted".into() });
}
Ok(())
}
}
/// Removes all files and folder paths recursively.
pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec) -> Result<(), Error> {
self.delete_specified_canonicalized(full_paths)?;
Ok(())
}
}
pub fn list() -> Result, Error> {
ensure_com_initialized();
unsafe {
let mut item_vec = Vec::new();
let recycle_bin: IShellItem =
SHGetKnownFolderItem(&FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT, HANDLE::default())?;
let pesi: IEnumShellItems = recycle_bin.BindToHandler(None, &BHID_EnumItems)?;
loop {
let mut fetched_count: u32 = 0;
let mut arr = [None];
pesi.Next(&mut arr, Some(&mut fetched_count as *mut u32))?;
if fetched_count == 0 {
break;
}
match &arr[0] {
Some(item) => {
let id = get_display_name(item, SIGDN_DESKTOPABSOLUTEPARSING)?;
let name = get_display_name(item, SIGDN_PARENTRELATIVE)?;
let item2: IShellItem2 = item.cast()?;
let original_location_variant = item2.GetProperty(&SCID_ORIGINAL_LOCATION)?;
let original_location_bstr = PropVariantToBSTR(&original_location_variant)?;
let original_location = OsString::from_wide(original_location_bstr.as_wide());
let date_deleted = get_date_deleted_unix(&item2)?;
// NTFS paths are valid Unicode according to this chart:
// https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations
// Converting a String back to OsString doesn't do extra work
item_vec.push(TrashItem {
id,
name: name.into_string().map_err(|original| Error::ConvertOsString { original })?.into(),
original_parent: PathBuf::from(original_location),
time_deleted: date_deleted,
});
}
None => {
break;
}
}
}
Ok(item_vec)
}
}
pub fn is_empty() -> Result {
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 {
ensure_com_initialized();
let id_as_wide = to_wide_path(&item.id);
let parsing_name = PCWSTR(id_as_wide.as_ptr());
let item: IShellItem = unsafe { SHCreateItemFromParsingName(parsing_name, None)? };
let is_dir = unsafe { item.GetAttributes(SFGAO_FOLDER)? } == SFGAO_FOLDER;
let size = if is_dir {
let pesi: IEnumShellItems = unsafe { item.BindToHandler(None, &BHID_EnumItems)? };
let mut size = 0;
loop {
let mut fetched_count: u32 = 0;
let mut arr = [None];
unsafe { pesi.Next(&mut arr, Some(&mut fetched_count as *mut u32))? };
if fetched_count == 0 {
break;
}
match &arr[0] {
Some(_item) => {
size += 1;
}
None => {
break;
}
}
}
TrashItemSize::Entries(size)
} else {
let item2: IShellItem2 = item.cast()?;
TrashItemSize::Bytes(unsafe { item2.GetUInt64(&PKEY_Size)? })
};
Ok(TrashItemMetadata { size })
}
pub fn purge_all(items: I) -> Result<(), Error>
where
I: IntoIterator,
::Item: Borrow,
{
ensure_com_initialized();
unsafe {
let pfo: IFileOperation = CoCreateInstance(&FileOperation as *const _, None, CLSCTX_ALL)?;
pfo.SetOperationFlags(FOF_NO_UI)?;
let mut at_least_one = false;
for item in items {
at_least_one = true;
let id_as_wide = to_wide_path(&item.borrow().id);
let parsing_name = PCWSTR(id_as_wide.as_ptr());
let trash_item: IShellItem = SHCreateItemFromParsingName(parsing_name, None)?;
pfo.DeleteItem(&trash_item, None)?;
}
if at_least_one {
pfo.PerformOperations()?;
}
Ok(())
}
}
pub fn restore_all(items: I) -> Result<(), Error>
where
I: IntoIterator
- ,
{
let items: Vec<_> = items.into_iter().collect();
// Do a quick and dirty check if the target items already exist at the location
// and if they do, return all of them, if they don't just go ahead with the processing
// without giving a damn.
// Note that this is not 'thread safe' meaning that if a paralell thread (or process)
// does this operation the exact same time or creates files or folders right after this check,
// then the files that would collide will not be detected and returned as part of an error.
// Instead Windows will display a prompt to the user whether they want to replace or skip.
for item in items.iter() {
let path = item.original_path();
if path.exists() {
return Err(Error::RestoreCollision { path, remaining_items: items });
}
}
ensure_com_initialized();
unsafe {
let pfo: IFileOperation = CoCreateInstance(&FileOperation as *const _, None, CLSCTX_ALL)?;
pfo.SetOperationFlags(FOF_NO_UI | FOFX_EARLYFAILURE)?;
for item in items.iter() {
let id_as_wide = to_wide_path(&item.id);
let parsing_name = PCWSTR(id_as_wide.as_ptr());
let trash_item: IShellItem = SHCreateItemFromParsingName(parsing_name, None)?;
let parent_path_wide = to_wide_path(&item.original_parent);
let orig_folder_shi: IShellItem = SHCreateItemFromParsingName(PCWSTR(parent_path_wide.as_ptr()), None)?;
let name_wstr = to_wide_path(&item.name);
pfo.MoveItem(&trash_item, &orig_folder_shi, PCWSTR(name_wstr.as_ptr()), None)?;
}
if !items.is_empty() {
pfo.PerformOperations()?;
}
Ok(())
}
}
unsafe fn get_display_name(psi: &IShellItem, sigdnname: SIGDN) -> Result {
let name = psi.GetDisplayName(sigdnname)?;
let result = wstr_to_os_string(name);
CoTaskMemFree(Some(name.0 as *const c_void));
Ok(result)
}
unsafe fn wstr_to_os_string(wstr: PWSTR) -> OsString {
let mut len = 0;
while *(wstr.0.offset(len)) != 0 {
len += 1;
}
let wstr_slice = std::slice::from_raw_parts(wstr.0, len as usize);
OsString::from_wide(wstr_slice)
}
unsafe fn get_date_deleted_unix(item: &IShellItem2) -> Result {
/// January 1, 1970 as Windows file time
const EPOCH_AS_FILETIME: u64 = 116444736000000000;
const HUNDREDS_OF_NANOSECONDS: u64 = 10000000;
let time = item.GetFileTime(&SCID_DATE_DELETED)?;
let time_u64 = ((time.dwHighDateTime as u64) << 32) | (time.dwLowDateTime as u64);
let rel_to_linux_epoch = time_u64 - EPOCH_AS_FILETIME;
let seconds_since_unix_epoch = rel_to_linux_epoch / HUNDREDS_OF_NANOSECONDS;
Ok(seconds_since_unix_epoch as i64)
}
struct CoInitializer {}
impl CoInitializer {
fn new() -> CoInitializer {
//let first = INITIALIZER_THREAD_COUNT.fetch_add(1, Ordering::SeqCst) == 0;
#[cfg(all(not(feature = "coinit_multithreaded"), not(feature = "coinit_apartmentthreaded")))]
{
0 = "THIS IS AN ERROR ON PURPOSE. Either the `coinit_multithreaded` or the `coinit_apartmentthreaded` feature must be specified";
}
let mut init_mode;
#[cfg(feature = "coinit_multithreaded")]
{
init_mode = COINIT_MULTITHREADED;
}
#[cfg(feature = "coinit_apartmentthreaded")]
{
init_mode = COINIT_APARTMENTTHREADED;
}
// These flags can be combined with either of coinit_multithreaded or coinit_apartmentthreaded.
if cfg!(feature = "coinit_disable_ole1dde") {
init_mode |= COINIT_DISABLE_OLE1DDE;
}
if cfg!(feature = "coinit_speed_over_memory") {
init_mode |= COINIT_SPEED_OVER_MEMORY;
}
let hr = unsafe { CoInitializeEx(None, init_mode) };
if hr.is_err() {
panic!("Call to CoInitializeEx failed. HRESULT: {:?}. Consider using `trash` with the feature `coinit_multithreaded`", hr);
}
CoInitializer {}
}
}
impl Drop for CoInitializer {
fn drop(&mut self) {
// TODO: This does not get called because it's a global static.
// Is there an atexit in Win32?
unsafe {
CoUninitialize();
}
}
}
thread_local! {
static CO_INITIALIZER: CoInitializer = CoInitializer::new();
}
fn ensure_com_initialized() {
CO_INITIALIZER.with(|_| {});
}