132 Commits
0.0.2 ... 0.1.6

Author SHA1 Message Date
Sreedev Kodichath
7bd88f1642 Merge pull request #49 from sreedevk/development
Version 0.1.6
2023-02-06 09:37:01 -05:00
sreedev
a65e22269c stabilize ui progress bars 2023-02-06 09:35:24 -05:00
sreedev
6e3fe4a37d cargo dist 2023-02-03 10:18:26 -05:00
sreedev
eadffb5fea doc fixes 2023-01-27 09:56:10 -05:00
sreedev
5532ded331 updated benchmarks 2023-01-27 09:54:39 -05:00
sreedev
7119f019d3 benchmark updates 2023-01-27 09:52:14 -05:00
sreedev
493cac1762 upgraded dependencies 2023-01-26 14:17:54 -05:00
sreedev
f0dbf05705 removed tokio & other unused dependencies 2023-01-26 14:07:50 -05:00
sreedev
ef1e9a1fce replace path String in File type with PathBuf 2023-01-26 00:04:17 -05:00
sreedev
d06ca7897d fix critical error 2023-01-25 23:12:52 -05:00
sreedev
b49940998b version 0.1.6 2023-01-25 21:27:31 -05:00
Sreedev Kodichath
ab44d1ed04 Merge pull request #47 from sreedevk/development
Version 0.1.5
2023-01-25 20:35:09 -05:00
sreedev
6b06798e8e updated README.md 2023-01-23 20:15:24 -05:00
sreedev
4d27da99f3 updated README.md 2023-01-23 20:09:15 -05:00
Sreedev Kodichath
91dce29bb4 Merge pull request #46 from sreedevk/feature/add_scan_control_args
[Feature] Add scan control args
2023-01-23 20:06:54 -05:00
sreedev
f9b6d57968 added short params 2023-01-23 20:05:14 -05:00
sreedev
5c86a080c4 Merge branch 'development' into feature/add_scan_control_args 2023-01-23 20:03:30 -05:00
sreedev
9f4d9139b6 added --min-depth --max-depth --follow-links and renamed --minsize to --min-size 2023-01-23 20:03:08 -05:00
Sreedev Kodichath
153109ef57 Merge pull request #45 from sreedevk/feature/make_dir_positional
make --dir a positional argument
2023-01-23 20:02:00 -05:00
sreedev
fa12f85b6a make --dir a positional argument 2023-01-23 19:12:55 -05:00
sreedev
7d66aeef6e version 0.1.5 2023-01-23 19:02:09 -05:00
Sreedev Kodichath
163e8d85c0 Merge pull request #38 from sreedevk/development
Version 0.1.4
2023-01-23 18:35:28 -05:00
sreedev
b1c508e9e7 versioning changes 2023-01-23 18:33:06 -05:00
Sreedev Kodichath
4125b18d67 Merge pull request #42 from sreedevk/feature/add-outputgen-spinner
Added more progress bars to avoid blank states
2023-01-23 18:30:56 -05:00
sreedev
10329015f7 added more progress bars to avoid blank states 2023-01-23 18:29:41 -05:00
Sreedev Kodichath
de6d2e325e Merge pull request #41 from beeb/globwalk-builder
refactor: use globwalk builder pattern
2023-01-23 17:50:09 -05:00
beeb
850c274adc refactor: use globwalk builder pattern 2023-01-23 22:28:48 +01:00
sreedev
31d35aae51 Merge branch 'main' into development 2023-01-23 16:07:26 -05:00
Sreedev Kodichath
087f36ac52 Merge pull request #37 from beeb/globwalk
Switch to globwalk
2023-01-23 16:06:14 -05:00
beeb
6f8b1d55df feat: switch to globwalk 2023-01-23 21:53:44 +01:00
sreedev
c6318a310b added benchmarks 2023-01-23 15:37:34 -05:00
Sreedev Kodichath
76c69ef8a0 Update README.md 2023-01-23 15:14:31 -05:00
sreedev
fd4e882aa8 updated README 2023-01-23 14:54:19 -05:00
sreedev
fd1d214d62 updated github workflows 2023-01-23 03:36:47 -05:00
sreedev
1a573fbf3f release github flow changes 2023-01-23 03:24:12 -05:00
sreedev
b12b662ba1 workflow updates 2023-01-23 03:19:57 -05:00
sreedev
96337b6b48 artifacts upload options added to github workflows 2023-01-23 02:39:37 -05:00
sreedev
02f70aa55f updated release github actions 2023-01-23 02:28:57 -05:00
Sreedev Kodichath
4a18b3fd91 Merge pull request #33 from sreedevk/development
Version 0.1.3
2023-01-23 02:20:59 -05:00
sreedev
876fdc69ac added version changes 2023-01-23 02:20:42 -05:00
sreedev
8acc3f8c8f added github workflow for building binaries with releases 2023-01-23 02:18:23 -05:00
Sreedev Kodichath
38f6c48452 Merge pull request #35 from sreedevk/dependabot/cargo/tokio-1.23.1
build(deps): bump tokio from 1.23.0 to 1.23.1
2023-01-21 19:31:49 -05:00
sreedev
3e1e9c0e2e Merge branch 'main' into development 2023-01-20 21:01:50 -05:00
Sreedev Kodichath
0674ab55bf Update README.md 2023-01-20 21:01:47 -05:00
dependabot[bot]
ccff95dfc5 build(deps): bump tokio from 1.23.0 to 1.23.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.23.0 to 1.23.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.23.0...tokio-1.23.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-21 01:57:43 +00:00
Sreedev Kodichath
53d28abb62 Merge pull request #32 from sreedevk/performance/glob_improvements
Performance/glob improvements
2023-01-20 19:17:39 -05:00
Sreedev Kodichath
ea6cf94952 Update README.md 2023-01-19 23:37:13 -05:00
sreedev
7b275d40d0 min opts 2 2023-01-19 23:27:40 -05:00
sreedev
30398c3ca9 min opts 2023-01-19 23:25:16 -05:00
sreedev
c0042fc9f7 version 0.1.2 release changes 2023-01-19 09:40:58 -05:00
Sreedev Kodichath
5aea0eb6f4 Merge pull request #28 from sreedevk/development
Version 0.1.2
2023-01-19 09:39:59 -05:00
Sreedev Kodichath
f0ff1ec325 Merge pull request #27 from beeb/sorted_by_size
Sort table results by ascending file size
2023-01-18 10:36:11 -05:00
Sreedev Kodichath
b267fcdedf Merge pull request #29 from beeb/cli_value_hint
feat(cli): add value hint for --dir argument
2023-01-18 10:28:15 -05:00
Valentin Bersier
84b194efca feat(cli): add value hint for --dir argument 2023-01-18 15:40:10 +01:00
Valentin Bersier
d162ca98ef docs: comment 2023-01-18 15:34:58 +01:00
Valentin Bersier
dfb73ceb5c feat: sort interactive results by descending size 2023-01-18 15:33:23 +01:00
Valentin Bersier
72ca7c7a44 Merge branch 'development' into sorted_by_size 2023-01-18 15:24:47 +01:00
Sreedev Kodichath
2f3cfd3162 Merge pull request #26 from sreedevk/feature/min-filesize
Min Size Filter Feature
2023-01-18 02:40:17 -05:00
sreedev
9b1f591c20 adds minsize option to README.md 2023-01-18 02:39:29 -05:00
sreedev
e24603c645 output enhancements 2023-01-18 02:34:08 -05:00
sreedev
0e624b042d added minsize filter 2023-01-18 02:30:48 -05:00
Valentin Bersier
e61d1f1475 perf: unwrap the size once 2023-01-18 08:07:51 +01:00
Valentin Bersier
8c76c6a3c8 refactor: simplify iterator to get size 2023-01-18 08:00:53 +01:00
Valentin Bersier
df3bea3d4f feat: display results in ascending size order 2023-01-18 07:59:51 +01:00
sreedev
7ace0bde63 scanner optimizations 2023-01-18 01:33:51 -05:00
sreedev
70de402eab performance updates in README.md 2023-01-18 00:45:24 -05:00
Sreedev Kodichath
01dd93a0ea Merge pull request #22 from sreedevk/development
Version 0.1.1
2023-01-18 00:35:51 -05:00
sreedev
8ac78fb856 version 0.1.1 2023-01-18 00:32:53 -05:00
Sreedev Kodichath
37a4c4d52d Merge pull request #23 from sreedevk/performance/incremental-hashing
Performance Improvements
2023-01-18 00:31:27 -05:00
sreedev
1ea5705474 fix confusing delete confirmation 2023-01-18 00:30:39 -05:00
sreedev
ea748c60d8 performance tweaks 2023-01-18 00:24:50 -05:00
sreedev
51f5f8e61a incremental hashing + remove sqlite + mmap file reads 2023-01-17 23:40:57 -05:00
Sreedev Kodichath
175c7579c4 Merge pull request #21 from sreedevk/code-opts
Code Optimizations
2023-01-13 21:03:58 -05:00
sreedev
81c96ce3b8 code optimizations 2023-01-13 20:54:09 -05:00
sreedev
0031891b8b remove unused code 2023-01-13 20:47:51 -05:00
Sreedev Kodichath
ae87e4e830 Create rust.yml 2023-01-13 07:32:28 -05:00
Sreedev Kodichath
284e168453 Update CONTRIBUTING.md 2023-01-13 01:09:31 -05:00
Sreedev Kodichath
a6511f2cf3 Merge pull request #20 from sreedevk/development
Version 0.0.9
2023-01-13 00:55:25 -05:00
sreedev
64d0106765 version changes 2023-01-13 00:53:46 -05:00
Sreedev Kodichath
c75b2eb1c9 Merge pull request #17 from ghfghfg23/main
Fix of "thread 'main' panicked at 'range start index 130 out of range…
2023-01-13 00:49:13 -05:00
Andrey Ryabov
b736853dfe Take 32 grapheme clusters instead of characters 2023-01-12 21:43:31 +02:00
Sreedev Kodichath
92290480a8 Update README.md 2023-01-11 19:41:23 -05:00
Sreedev Kodichath
b7f775e04c Update README.md 2023-01-11 19:39:02 -05:00
sreedev
12295d7847 version 0.0.8 2023-01-11 19:28:34 -05:00
Sreedev Kodichath
d81f499db3 Create CONTRIBUTING.md 2023-01-11 19:14:39 -05:00
Sreedev Kodichath
9e1360aeb4 Merge pull request #18 from sreedevk/progress-bar
[Feature] Add Progress Bar When Indexing Files
2023-01-11 18:56:40 -05:00
Sreedev Kodichath
38ea37711f Update README.md 2023-01-11 18:37:10 -05:00
Andrey Ryabov
2294471b50 Fix of "thread 'main' panicked at 'range start index 130 out of range for slice of length 104', src/output.rs:21:9" 2023-01-11 23:01:10 +02:00
sreedev
dbf504fc17 added progress bar 2023-01-11 15:55:31 -05:00
Sreedev Kodichath
06efb2ed6f Update issue templates 2023-01-11 07:45:51 -05:00
sreedev
95af6c4a70 updated docs to show interactive mode 2023-01-10 20:28:53 -05:00
Sreedev Kodichath
dd4b051378 Merge pull request #16 from sreedevk/interactive-mode
Interactive mode
2023-01-10 20:26:38 -05:00
sreedev
533f81f724 version 0.0.7 2023-01-10 20:26:03 -05:00
sreedev
471e60fa6c added delete options 2023-01-10 20:23:53 -05:00
sreedev
3fd4869869 UI setup complete 2023-01-10 20:13:45 -05:00
sreedev
99b87cf7fa Merge branch 'main' into interactive-mode 2023-01-09 22:55:14 -05:00
Sreedev Kodichath
b826dbe118 Update README.md 2023-01-09 22:54:19 -05:00
Sreedev Kodichath
2c75a017ce Merge pull request #14 from sreedevk/improved-ui
* Improved Duplicates Table Printing
* Added Prettytable-rs crate
* Versioning for 0.0.6
2023-01-09 22:48:59 -05:00
sreedev
b5bb58b3ed version changes 2023-01-09 22:47:54 -05:00
sreedev
6e412fc59c table display improvements 2023-01-09 22:40:37 -05:00
sreedev
a0083cb571 table printing added 2023-01-09 22:15:44 -05:00
sreedev
0f33b4d6b5 added interactive param 2023-01-09 11:13:29 -05:00
sreedev
96fe667d3f version 0.0.5 2023-01-09 11:08:20 -05:00
sreedev
c32e64450f bug fix: connection result issue 2023-01-09 11:04:29 -05:00
Sreedev Kodichath
85acbded46 Merge pull request #13 from beeb/panics
Fix some more unhandled errors
2023-01-09 11:03:12 -05:00
Sreedev Kodichath
f12cecc9ee Merge branch 'main' into panics 2023-01-09 11:02:56 -05:00
sreedev
05f513735e tempdir fixes 2023-01-09 11:01:52 -05:00
Valentin Bersier
552f6c73f2 Merge branch 'main' into panics 2023-01-09 16:59:42 +01:00
Valentin Bersier
138b66038f refactor: various clippy fixes 2023-01-09 16:57:43 +01:00
Sreedev Kodichath
d8e1de169d Merge pull request #12 from dhruvasagar/fix/temp_file_path
Fix Temporary File Path #6
2023-01-09 10:48:28 -05:00
Valentin Bersier
bc170f139e style: remove trailing spaces 2023-01-09 16:46:12 +01:00
Valentin Bersier
c76ad81a55 refactor: do no unwrap in database.rs 2023-01-09 16:41:55 +01:00
Valentin Bersier
254e61cabe fix: clippy warnings 2023-01-09 16:27:52 +01:00
Valentin Bersier
a76163f24f style: format 2023-01-09 16:21:13 +01:00
Valentin Bersier
27fef21be0 fix: no unwrap in params.rs 2023-01-09 16:17:50 +01:00
Dhruva Sagar
8b00faf075 Fix Temporary File Path #6
Better cross platform support
2023-01-09 10:53:20 +05:30
sreedev
e158a8267a added authors & updated version 2023-01-08 17:16:46 -05:00
Sreedev Kodichath
e6f93ce3d6 Merge pull request #9 from beeb/scanner
Refactor scanner
2023-01-08 17:11:36 -05:00
Sreedev Kodichath
011da05c59 Merge pull request #10 from beeb/output
Refactor output.rs
2023-01-08 17:11:04 -05:00
beeb
6087abe960 refactor: no need for into_iter 2023-01-08 13:24:08 +01:00
beeb
5eba7cdc30 fix: still print items even if file size or modified time cannot be retrieved 2023-01-08 13:21:47 +01:00
beeb
5a65550e58 refactor: output.rs 2023-01-08 13:20:15 +01:00
beeb
0b5effd06e refactor: no need to consume pathbuf iterator 2023-01-08 13:09:43 +01:00
beeb
cc243f413a refactor: no need to consume iterator items 2023-01-08 13:08:27 +01:00
beeb
ad467d70a8 style: combine use statements for std 2023-01-08 12:20:27 +01:00
beeb
4490ea4c69 style: combine use statements for database 2023-01-08 12:19:56 +01:00
beeb
8ed0b13ff0 style: order imports 2023-01-08 12:19:25 +01:00
beeb
be7e8d38ae refactor: avoid unwraps 2023-01-08 11:48:49 +01:00
beeb
c9694dea09 fix: clippy warnings 2023-01-08 11:22:43 +01:00
sreedev
3a7ac7a332 version 0.0.3 2023-01-07 07:04:03 -05:00
Sreedev Kodichath
51521f1641 Merge pull request #5 from sreedevk/v003
Version 0.0.3
2023-01-07 07:02:40 -05:00
sreedev
b23db06847 fix: cyrillic characters output fix 2023-01-07 07:01:29 -05:00
21 changed files with 1162 additions and 660 deletions

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] Title"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
** Runtime Info **
App Arguments: [e.g. `-i --nocache`]
Install Type: [e.g. `cargo install`]
App Version: [e.g. v0.0.7]
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Platform Details (please complete the following information):**
- OS: [e.g. Arch Linux]
- Terminal Emulator: [e.g Alacritty]
- Shell [e.g. Zshell]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature] Title"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

136
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
# CI that:
#
# * checks for a Git Tag that looks like a release ("v1.2.0")
# * creates a Github Release™
# * builds binaries/packages with cargo-dist
# * uploads those packages to the Github Release™
#
# Note that the Github Release™ will be created before the packages,
# so there will be a few minutes where the release has no packages
# and then they will slowly trickle in, possibly failing. To make
# this more pleasant we mark the release as a "draft" until all
# artifacts have been successfully uploaded. This allows you to
# choose what to do with partial successes and avoids spamming
# anyone with notifications before the release is actually ready.
name: Release
permissions:
contents: write
# This task will run whenever you push a git tag that looks like
# a version number. We just look for `v` followed by at least one number
# and then whatever. so `v1`, `v1.0.0`, and `v1.0.0-prerelease` all work.
#
# If there's a prerelease-style suffix to the version then the Github Release™
# will be marked as a prerelease (handled by taiki-e/create-gh-release-action).
#
# Note that when generating links to uploaded artifacts, cargo-dist will currently
# assume that your git tag is always v{VERSION} where VERSION is the version in
# the published package's Cargo.toml (this is the default behaviour of cargo-release).
# In the future this may be made more robust/configurable.
on:
push:
tags:
- v[0-9]+.*
env:
ALL_CARGO_DIST_TARGET_ARGS: --target=x86_64-unknown-linux-gnu --target=x86_64-apple-darwin --target=x86_64-pc-windows-msvc
ALL_CARGO_DIST_INSTALLER_ARGS:
jobs:
# Create the Github Release™ so the packages have something to be uploaded to
create-release:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.create-gh-release.outputs.computed-prefix }}${{ steps.create-gh-release.outputs.version }}
steps:
- uses: actions/checkout@v3
- id: create-gh-release
uses: taiki-e/create-gh-release-action@v1
with:
draft: true
# (required) GitHub token for creating GitHub Releases.
token: ${{ secrets.GITHUB_TOKEN }}
# Build and packages all the things
upload-artifacts:
needs: create-release
strategy:
matrix:
# For these target platforms
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
install-dist: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- target: x86_64-apple-darwin
os: macos-11
install-dist: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- target: x86_64-pc-windows-msvc
os: windows-2019
install-dist: irm 'https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.ps1' | iex
runs-on: ${{ matrix.os }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
run: rustup update stable && rustup default stable
- name: Install cargo-dist
run: ${{ matrix.install-dist }}
- name: Run cargo-dist
# This logic is a bit janky because it's trying to be a polyglot between
# powershell and bash since this will run on windows, macos, and linux!
# The two platforms don't agree on how to talk about env vars but they
# do agree on 'cat' and '$()' so we use that to marshal values between commmands.
run: |
# Actually do builds and make zips and whatnot
cargo dist --target=${{ matrix.target }} --output-format=json > dist-manifest.json
echo "dist ran successfully"
cat dist-manifest.json
# Parse out what we just built and upload it to the Github Release™
cat dist-manifest.json | jq --raw-output ".releases[].artifacts[].path" > uploads.txt
echo "uploading..."
cat uploads.txt
gh release upload ${{ needs.create-release.outputs.tag }} $(cat uploads.txt)
echo "uploaded!"
# Compute and upload the manifest for everything
upload-manifest:
needs: create-release
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
run: rustup update stable && rustup default stable
- name: Install cargo-dist
run: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- name: Run cargo-dist manifest
run: |
# Generate a manifest describing everything
cargo dist manifest --no-local-paths --output-format=json $ALL_CARGO_DIST_TARGET_ARGS $ALL_CARGO_DIST_INSTALLER_ARGS > dist-manifest.json
echo "dist manifest ran successfully"
cat dist-manifest.json
# Upload the manifest to the Github Release™
gh release upload ${{ needs.create-release.outputs.tag }} dist-manifest.json
echo "uploaded manifest!"
# Edit the Github Release™ title/body to match what cargo-dist thinks it should be
CHANGELOG_TITLE=$(cat dist-manifest.json | jq --raw-output ".releases[].changelog_title")
cat dist-manifest.json | jq --raw-output ".releases[].changelog_body" > new_dist_changelog.md
gh release edit ${{ needs.create-release.outputs.tag }} --title="$CHANGELOG_TITLE" --notes-file=new_dist_changelog.md
echo "updated release notes!"
# Mark the Github Release™ as a non-draft now that everything has succeeded!
publish-release:
needs: [create-release, upload-artifacts, upload-manifest]
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: mark release as non-draft
run: |
gh release edit ${{ needs.create-release.outputs.tag }} --draft=false

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target
/test_data
.envrc

28
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,28 @@
## How to contribute to Deduplicator
#### **Did you find a bug?**
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/sreedevk/deduplicator/issues).
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/sreedevk/deduplicator/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
* If possible, use the [bug report template](https://github.com/sreedevk/deduplicator/blob/main/.github/ISSUE_TEMPLATE/bug_report.md) to create the issue.
#### **Would you like to write a fix for the bug?**
* Assign the Issue to yourself (if unassigned) before you start working in order to avoid any conficts.
* Open a new GitHub pull request with the patch.
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number.
* Make sure that the PR points to the development branch.
#### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Deduplicator will generally not be accepted/
#### **Do you intend to add a new feature or change an existing one?**
* First open an issue with the sugggestion using the [feature request template](https://github.com/sreedevk/deduplicator/blob/main/.github/ISSUE_TEMPLATE/feature-request.md)
* Do not create a PR before one of the core contributors has conveyed acceptance for a feature request.
#### **Do you have questions about the source code?**
* If you have a question, raise an issue in the repository with a "question" label.

599
Cargo.lock generated
View File

@@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -41,10 +50,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.11.1"
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bstr"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
@@ -53,16 +84,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.3.0"
name = "bytesize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"
[[package]]
name = "cc"
@@ -93,9 +118,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.0.32"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
@@ -108,9 +133,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.0.21"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
@@ -121,9 +146,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
@@ -149,6 +174,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "console"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
dependencies = [
"encode_unicode 0.3.6",
"lazy_static",
"libc",
"unicode-width",
"windows-sys",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@@ -199,35 +237,32 @@ dependencies = [
]
[[package]]
name = "crossterm"
version = "0.25.0"
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
"bstr 0.2.17",
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"winapi",
"memchr",
]
[[package]]
name = "cxx"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -237,9 +272,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200"
dependencies = [
"cc",
"codespan-reporting",
@@ -252,39 +287,74 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea"
[[package]]
name = "cxxbridge-macro"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dashmap"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
"rayon",
]
[[package]]
name = "deduplicator"
version = "0.0.1"
version = "0.1.6"
dependencies = [
"anyhow",
"bytesize",
"chrono",
"clap",
"colored",
"crossterm",
"dashmap",
"fxhash",
"glob",
"humansize",
"globwalk",
"indicatif",
"itertools",
"memmap2",
"prettytable-rs",
"rayon",
"sqlite",
"thiserror",
"tokio",
"tui",
"unicode-segmentation",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
@@ -293,6 +363,18 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "errno"
version = "0.2.8"
@@ -314,6 +396,12 @@ dependencies = [
"libc",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fxhash"
version = "0.2.1"
@@ -324,10 +412,45 @@ dependencies = [
]
[[package]]
name = "glob"
version = "0.3.0"
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "globset"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr 1.1.0",
"fnv",
"log",
"regex",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags",
"ignore",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
@@ -353,15 +476,6 @@ dependencies = [
"libc",
]
[[package]]
name = "humansize"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e682e2bd70ecbcce5209f11a992a4ba001fea8e60acf7860ce007629e6d2756"
dependencies = [
"libm",
]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@@ -387,10 +501,40 @@ dependencies = [
]
[[package]]
name = "io-lifetimes"
version = "1.0.3"
name = "ignore"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
dependencies = [
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "indicatif"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729"
dependencies = [
"console",
"number_prefix",
"portable-atomic",
"rayon",
"unicode-width",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
@@ -417,6 +561,12 @@ dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "js-sys"
version = "0.3.60"
@@ -438,12 +588,6 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
@@ -484,6 +628,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.7.1"
@@ -493,18 +646,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -535,10 +676,16 @@ dependencies = [
]
[[package]]
name = "once_cell"
version = "1.16.0"
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "os_str_bytes"
@@ -546,21 +693,11 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[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.5"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
@@ -570,16 +707,24 @@ dependencies = [
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
name = "portable-atomic"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b"
[[package]]
name = "pkg-config"
version = "0.3.26"
name = "prettytable-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
dependencies = [
"csv",
"encode_unicode 1.0.0",
"is-terminal",
"lazy_static",
"term",
"unicode-width",
]
[[package]]
name = "proc-macro-error"
@@ -607,9 +752,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.49"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
@@ -635,9 +780,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.10.1"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -655,10 +800,44 @@ dependencies = [
]
[[package]]
name = "rustix"
version = "0.36.5"
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
@@ -668,6 +847,27 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -681,34 +881,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "signal-hook"
version = "0.3.14"
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "smallvec"
@@ -716,46 +892,6 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "sqlite"
version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12e072cb5fb89b3fe5e9c9584676348feb503f9fb3ae829d9868171bc5372d48"
dependencies = [
"libc",
"sqlite3-sys",
]
[[package]]
name = "sqlite3-src"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1815a7a02c996eb8e5c64f61fcb6fd9b12e593ce265c512c5853b2513635691"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "sqlite3-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d47c99824fc55360ba00caf28de0b8a0458369b832e016a64c13af0ad9fbb9ee"
dependencies = [
"libc",
"sqlite3-src",
]
[[package]]
name = "strsim"
version = "0.10.0"
@@ -774,10 +910,21 @@ dependencies = [
]
[[package]]
name = "termcolor"
version = "1.1.3"
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
@@ -802,6 +949,15 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.45"
@@ -813,50 +969,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "tokio"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tui"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
dependencies = [
"bitflags",
"cassowary",
"crossterm",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
@@ -881,6 +993,17 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@@ -995,42 +1118,42 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

View File

@@ -1,24 +1,36 @@
[package]
name = "deduplicator"
version = "0.0.2"
version = "0.1.6"
edition = "2021"
description = "find,filter,delete Duplicates"
license = "MIT"
authors = [
"Sreedev Kodichath <sreedevpadmakumar@gmail.com>",
"Valentin Bersier <vbersier@gmail.com>",
"Dhruva Sagar <dhruva.sagar@gmail.com>",
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.68"
bytesize = "1.1.0"
chrono = "0.4.23"
clap = { version = "4.0.32", features = ["derive"] }
colored = "2.0.0"
crossterm = "0.25.0"
dashmap = { version = "5.4.0", features = ["rayon"] }
fxhash = "0.2.1"
glob = "0.3.0"
humansize = "2.1.2"
globwalk = "0.8.1"
indicatif = { version = "0.17.2", features = ["rayon"] }
itertools = "0.10.5"
memmap2 = "0.5.8"
prettytable-rs = "0.10.0"
rayon = "1.6.1"
sqlite = "0.30.3"
thiserror = "1.0.38"
tokio = { version = "1.23.0", features = ["full"] }
tui = "0.19.0"
unicode-segmentation = "1.10.0"
# generated by 'cargo dist init'
[profile.dist]
inherits = "release"
debug = true
split-debuginfo = "packed"

132
README.md
View File

@@ -4,42 +4,120 @@
Find, Sort, Filter & Delete duplicate files
</p>
<p align="center">
NOTE: This project is still being developed. At the moment, as shown in the screenshot below, deduplicator is able to scan through and list duplicates with and without caching. Contributions are welcome.
</p>
<h2 align="center">Usage</h2>
## Usage
```bash
Usage: deduplicator [OPTIONS]
Usage: deduplicator [OPTIONS] [scan_dir_path]
Arguments:
[scan_dir_path] Run Deduplicator on dir different from pwd (e.g., ~/Pictures )
Options:
-t, --types <TYPES> Filetypes to deduplicate (default = all)
--dir <DIR> Run Deduplicator on dir different from pwd
-n, --nocache Don't use cache for indexing files (default = true)
-h, --help Print help information
-V, --version Print version information
-t, --types <TYPES> Filetypes to deduplicate [default = all]
-i, --interactive Delete files interactively
-s, --min-size <MIN_SIZE> Minimum filesize of duplicates to scan (e.g., 100B/1K/2M/3G/4T) [default: 1b]
-d, --max-depth <MAX_DEPTH> Max Depth to scan while looking for duplicates
--min-depth <MIN_DEPTH> Min Depth to scan while looking for duplicates
-f, --follow-links Follow links while scanning directories
-h, --help Print help information
-V, --version Print version information
```
### Examples
```bash
# Scan for duplicates recursively from the current dir, only look for png, jpg & pdf file types & interactively delete files
deduplicator -t pdf,jpg,png -i
# Scan for duplicates recursively from the ~/Pictures dir, only look for png, jpeg, jpg & pdf file types & interactively delete files
deduplicator ~/Pictures/ -t png,jpeg,jpg,pdf -i
# Scan for duplicates in the ~/Pictures without recursing into subdirectories
deduplicator ~/Pictures --max-depth 0
# look for duplicates in the ~/.config directory while also recursing into symbolic link paths
deduplicator ~/.config --follow-links
# scan for duplicates that are greater than 100mb in the ~/Media directory
deduplicator ~/Media --min-size 100mb
```
<h2 align="center">Installation</h2>
## Installation
<p align="center">Currently, deduplicator is only installable via rust's cargo package manager</p>
### Cargo Install
#### Stable
```bash
$ cargo install deduplicator
```
#### Nightly
if you'd like to install with nightly features, you can use
```bash
$ cargo install --git https://github.com/sreedevk/deduplicator
```
Please note that if you use a version manager to install rust (like asdf), you need to reshim (`asdf reshim rust`).
### Linux (Pre-built Binary)
you can download the pre-built binary from the [Releases](https://github.com/sreedevk/deduplicator/releases) page.
download the `deduplicator-x86_64-unknown-linux-gnu.tar.gz` for linux. Once you have the tarball file with the executable,
you can follow these steps to install:
```bash
$ tar -zxvf deduplicator-x86_64-unknown-linux-gnu.tar.gz
$ sudo mv deduplicator /usr/bin/
```
### Mac OS (Pre-built Binary)
you can download the pre-build binary from the [Releases](https://github.com/sreedevk/deduplicator/releases) page.
download the `deduplicator-x86_64-apple-darwin.tar.gz` tarball for mac os. Once you have the tarball file with the executable, you can follow these steps to install:
```bash
$ tar -zxvf deduplicator-x86_64-unknown-linux-gnu.tar.gz
$ sudo mv deduplicator /usr/bin/
```
### Windows (Pre-built Binary)
you can download the pre-build binary from the [Releases](https://github.com/sreedevk/deduplicator/releases) page.
download the `deduplicator-x86_64-pc-windows-msvc.zip` zip file for windows. unzip the `zip` file & move the `deduplicator.exe` to a location in the PATH system environment variable.
Note: If you Run into an msvc error, please install MSCV from [here](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170)
## Performance
Deduplicator uses size comparison and fxhash (a non non-cryptographic hashing algo) to quickly scan through large number of files to find duplicates. its also highly parallel (uses rayon and dashmap). I was able to scan through 120GB of files (Videos, PDFs, Images) in ~300ms. checkout the benchmarks
## benchmarks
| Command | Dirsize | Filecount | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|:---|---:|---:|---:|---:|---:|
| `deduplicator ~/Data/tmp` | (~120G) | 721 files | 33.5 ± 28.6 | 25.3 | 151.5 | 1.87 ± 1.60 |
| `deduplicator ~/Data/books` | (~8.6G) | 1419 files | 24.5 ± 1.0 | 22.9 | 28.1 | 1.37 ± 0.08 |
| `deduplicator ~/Data/books --min-size 10M` | (~8.6G) | 1419 files | 17.9 ± 0.7 | 16.8 | 20.0 | 1.00 |
| `deduplicator ~/Data/ --types pdf,jpg,png,jpeg` | (~290G) | 104222 files | 1207.2 ± 37.0 | 1172.2 | 1287.7 | 67.27 ± 3.33 |
* The last entry is lower because of the number of files deduplicator had to go through (~660895 Files). The average size of the files rarely affect the performance of deduplicator.
These benchmarks were run using [hyperfine](https://github.com/sharkdp/hyperfine). Here are the specs of the machine used to benchmark deduplicator:
```
cargo install deduplicator
OS: Arch Linux x86_64
Host: Precision 5540
Kernel: 5.15.89-1-lts
Uptime: 4 hours, 44 mins
Shell: zsh 5.9
Terminal: kitty
CPU: Intel i9-9880H (16) @ 4.800GHz
GPU: NVIDIA Quadro T2000 Mobile / Max-Q
GPU: Intel CoffeeLake-H GT2 [UHD Graphics 630]
Memory: 31731MiB (~32GiB)
```
<p align="center">
note that if you use a version manager to install rust (like asdf), you need to reshim (`asdf reshim rust`).
</p>
<h2 align="center">Performance</h2>
## Screenshots
<p align="center">
Deduplicator uses fxhash (a non-cryptographic hashing algorithm) which is extremely fast. As a result, deduplicator is able to process huge amounts of data in a couple of seconds.</p>
<p align="center">
While testing, Deduplicator was able to go through 8.6GB of pdf files and detect duplicates in 2.9 seconds
</p>
<h2 align="center">Screenshots</h2>
![_039](https://user-images.githubusercontent.com/36154121/210031222-d8b79143-5a1e-47ca-926e-8855d5bbab60.png)
![](https://user-images.githubusercontent.com/36154121/213618143-e5182e39-731e-4817-87dd-1a6a0f38a449.gif)

18
src/app.rs Normal file
View File

@@ -0,0 +1,18 @@
use crate::output;
use crate::params::Params;
use crate::scanner;
use anyhow::Result;
pub struct App;
impl App {
pub fn init(app_args: &Params) -> Result<()> {
let duplicates = scanner::duplicates(app_args)?;
match app_args.interactive {
true => output::interactive(duplicates, app_args),
false => output::print(duplicates, app_args),
}
Ok(())
}
}

View File

@@ -1,28 +0,0 @@
use std::time::Duration;
use crossterm::event::{self, KeyCode, KeyEvent};
use anyhow::Result;
use super::events;
pub struct EventHandler;
impl EventHandler {
pub fn init() -> Result<events::Event> {
if crossterm::event::poll(Duration::from_millis(10))? {
match event::read()? {
event::Event::Key(keycode) => Self::handle_keypress(keycode),
_ => Ok(events::Event::Noop),
}
} else {
Ok(events::Event::Noop)
}
}
fn handle_keypress(keyevent: KeyEvent) -> Result<events::Event> {
match keyevent.code {
KeyCode::Char('q') => Ok(events::Event::Exit),
_ => Ok(events::Event::Noop)
}
}
}

View File

@@ -1,4 +0,0 @@
pub enum Event {
Exit,
Noop
}

View File

View File

@@ -1,80 +0,0 @@
mod event_handler;
mod events;
mod ui;
mod formatter;
use crate::database;
use crate::output;
use crate::params::Params;
use crate::scanner;
use anyhow::{anyhow, Result};
use crossterm::{event, execute, terminal};
use event_handler::EventHandler;
use std::io;
use std::thread;
use std::time::Duration;
use tui::{
backend::CrosstermBackend,
widgets::{Block, Borders, Widget},
Terminal,
};
use ui::Ui;
pub struct App;
impl App {
pub fn init(app_args: &Params) -> Result<()> {
// let mut term = Self::init_terminal()?;
let connection = database::get_connection(&app_args)?;
let duplicates = scanner::duplicates(&app_args, &connection)?;
// Self::init_render_loop(&mut term)?;
// Self::cleanup(&mut term)?;
output::print(duplicates, &app_args); /* TODO: APP TUI INIT FUNCTION */
Ok(())
}
fn cleanup(term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()> {
terminal::disable_raw_mode()?;
execute!(
term.backend_mut(),
terminal::LeaveAlternateScreen,
event::DisableMouseCapture
)?;
term.show_cursor()?;
Ok(())
}
fn render_cycle(term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()> {
match EventHandler::init()? {
events::Event::Noop => Ui::render_frame(term),
events::Event::Exit => Err(anyhow!("Exit")),
}
}
fn init_render_loop(term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()> {
loop {
match Self::render_cycle(term) {
Ok(_) => continue,
Err(_) => break,
}
}
Ok(())
}
fn init_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
terminal::enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(
stdout,
terminal::EnterAlternateScreen,
event::EnableMouseCapture
)?;
let backend = CrosstermBackend::new(stdout);
Ok(Terminal::new(backend)?)
}
}

View File

@@ -1,53 +0,0 @@
use anyhow::Result;
use std::io;
use tui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, List, ListItem, Widget},
Frame, Terminal,
};
pub struct Ui;
impl Ui {
fn generate_file_list() -> impl Widget {
let tasks: Vec<ListItem> = vec!["Sreedev"; 100]
.into_iter()
.map(|item| ListItem::new(vec![Spans::from(Span::raw(item))]))
.collect();
List::new(tasks)
.block(Block::default().borders(Borders::ALL).title("List"))
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
.highlight_symbol("> ")
}
fn generate_info_bar() -> impl Widget {
Block::default().title("Description").borders(Borders::ALL)
}
fn generate_file_desc() -> impl Widget {
Block::default().title("Description").borders(Borders::ALL)
}
pub fn render_frame(term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()> {
term.draw(|f| {
let windows = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Ratio(2, 16), Constraint::Ratio(14, 16)].as_ref())
.split(f.size());
let subwindows = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref())
.split(windows[1]);
f.render_widget(Self::generate_info_bar(), windows[0]);
f.render_widget(Self::generate_file_list(), subwindows[0]);
f.render_widget(Self::generate_file_desc(), subwindows[1]);
})?;
Ok(())
}
}

View File

@@ -1,83 +0,0 @@
use anyhow::Result;
use crate::params::Params;
#[derive(Debug, Clone)]
pub struct File {
pub path: String,
pub hash: String,
}
pub fn get_connection(args: &Params) -> Result<sqlite::Connection, sqlite::Error> {
let connection_url = match args.nocache {
false => "/tmp/deduplicator.db",
true => ":memory:"
};
sqlite::open(connection_url).and_then(|conn| {
setup(&conn).ok();
Ok(conn)
})
}
pub fn setup(connection: &sqlite::Connection) -> Result<()> {
let query = "CREATE TABLE files (file_identifier STRING, hash STRING)";
connection.execute(query).ok();
Ok(())
}
pub fn put(file: &File, connection: &sqlite::Connection) -> Result<()> {
let query = format!(
"INSERT INTO files (file_identifier, hash) VALUES (\"{}\", \"{}\")",
file.path, file.hash
);
let result = connection.execute(query)?;
Ok(result)
}
pub fn indexed_paths(connection: &sqlite::Connection) -> Result<Vec<File>> {
let query = format!(
"SELECT * FROM files"
);
let result: Vec<File> = connection
.prepare(query)?
.into_iter()
.map(|row_result| row_result.unwrap())
.map(|row| {
let path = row.read::<&str, _>("file_identifier").to_string();
let hash = row.read::<i64, _>("hash").to_string();
File { path, hash }
})
.collect();
Ok(result)
}
pub fn duplicate_hashes(connection: &sqlite::Connection, path: &String) -> Result<Vec<File>> {
let query = format!(
"
SELECT a.* FROM files a
JOIN (SELECT file_identifier, hash, COUNT(*)
FROM files
GROUP BY hash
HAVING count(*) > 1 ) b
ON a.hash = b.hash
WHERE a.file_identifier LIKE \"{}%\"
ORDER BY a.file_identifier
", path
);
let result: Vec<File> = connection
.prepare(query)?
.into_iter()
.map(|row_result| row_result.unwrap())
.map(|row| {
let path = row.read::<&str, _>("file_identifier").to_string();
let hash = row.read::<i64, _>("hash").to_string();
File { path, hash }
})
.collect();
Ok(result)
}

21
src/file_manager.rs Normal file
View File

@@ -0,0 +1,21 @@
use anyhow::Result;
use colored::Colorize;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct File {
pub path: PathBuf,
pub size: Option<u64>,
pub hash: Option<String>,
}
pub fn delete_files(files: Vec<File>) -> Result<()> {
files.into_iter().for_each(|file| {
match std::fs::remove_file(file.path.clone()) {
Ok(_) => println!("{}: {}", "DELETED".green(), file.path.display()),
Err(_) => println!("{}: {}", "FAILED".red(), file.path.display())
}
});
Ok(())
}

12
src/filters.rs Normal file
View File

@@ -0,0 +1,12 @@
use crate::file_manager::File;
use crate::params::Params;
pub fn is_file_gt_min_size(app_opts: &Params, file: &File) -> bool {
match app_opts.get_min_size() {
Some(msize) => match file.size {
Some(fsize) => fsize >= msize,
None => true,
},
None => true,
}
}

View File

@@ -1,14 +1,14 @@
mod params;
mod database;
mod output;
mod scanner;
mod app;
mod file_manager;
mod output;
mod params;
mod scanner;
mod filters;
use anyhow::Result;
use clap::Parser;
use app::App;
use clap::Parser;
#[tokio::main]
async fn main() -> Result<()> {
fn main() -> Result<()> {
App::init(&params::Params::parse())
}

View File

@@ -1,66 +1,217 @@
use crate::database::File;
use crate::file_manager::{self, File};
use crate::params::Params;
use anyhow::Result;
use chrono::offset::Utc;
use chrono::DateTime;
use colored::Colorize;
use humansize::{format_size, DECIMAL};
use std::{collections::HashMap, fs};
use crate::params::Params;
use dashmap::DashMap;
use indicatif::{ProgressBar, ProgressIterator, ProgressStyle};
use itertools::Itertools;
use prettytable::{format, row, Table};
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use std::{fs, io};
use unicode_segmentation::UnicodeSegmentation;
fn format_path(path: &String, opts: &Params) -> String {
let display_path = path.replace(&opts.get_directory().unwrap(), "");
let display_range = if display_path.len() > 32 {
&display_path[(display_path.len() - 32)..]
fn format_path(path: &Path, opts: &Params) -> Result<String> {
let display_path = path
.to_string_lossy()
.replace(opts.get_directory()?.to_string_lossy().as_ref(), "");
let display_range = if display_path.chars().count() > 32 {
display_path
.graphemes(true)
.collect::<Vec<&str>>()
.into_iter()
.rev()
.take(32)
.rev()
.collect()
} else {
&display_path[..]
display_path
};
format!("...{}", display_range)
Ok(format!("...{display_range:<32}"))
}
fn file_size(path: &String) -> String {
let mdata = fs::metadata(path).unwrap();
let formatted_size = format_size(mdata.len(), DECIMAL);
format!("{}", formatted_size)
fn file_size(file: &File) -> Result<String> {
Ok(format!("{:>12}", bytesize::ByteSize::b(file.size.unwrap())))
}
fn modified_time(path: &String) -> String {
let mdata = fs::metadata(path).unwrap();
let modified_time: DateTime<Utc> = mdata.modified().unwrap().into();
fn modified_time(path: &Path) -> Result<String> {
let mdata = fs::metadata(path)?;
let modified_time: DateTime<Utc> = mdata.modified()?.into();
modified_time.format("%Y-%m-%d %H:%M:%S").to_string()
Ok(modified_time.format("%Y-%m-%d %H:%M:%S").to_string())
}
fn print_divider() {
println!("-------------------+-------------------------------------+------------------+----------------------------------+");
fn scan_group_instruction() -> Result<String> {
println!("\nEnter the indices of the files you want to delete.");
println!("You can enter multiple files using commas to seperate file indices.");
println!("example: 1,2");
print!("\n> ");
std::io::stdout().flush()?;
let mut user_input = String::new();
io::stdin().read_line(&mut user_input)?;
Ok(user_input)
}
pub fn print(duplicates: Vec<File>, opts: &Params) {
print_divider();
println!(
"| {0: <16} | {1: <35} | {2: <16} | {3: <32} |",
"hash", "filename", "size", "updated_at"
);
print_divider();
fn scan_group_confirmation() -> Result<bool> {
print!("\nconfirm? [y/N]: ");
std::io::stdout().flush()?;
let mut user_input = String::new();
io::stdin().read_line(&mut user_input)?;
let mut dup_index: HashMap<String, Vec<File>> = HashMap::new();
match user_input.trim() {
"Y" | "y" => Ok(true),
_ => Ok(false),
}
}
duplicates.into_iter().for_each(|file| {
dup_index
.entry(file.hash.clone())
.and_modify(|value| value.push(file.clone()))
.or_insert(vec![file]);
});
fn process_group_action(duplicates: &Vec<File>, dup_index: usize, dup_size: usize, table: Table) {
println!("\nDuplicate Set {} of {}\n", dup_index + 1, dup_size);
table.printstd();
let files_to_delete = scan_group_instruction().unwrap_or_default();
let parsed_file_indices = files_to_delete
.trim()
.split(',')
.filter(|element| !element.is_empty())
.map(|index| index.parse::<usize>().unwrap_or_default())
.collect::<Vec<usize>>();
dup_index.into_iter().for_each(|(_, group)| {
group.into_iter().for_each(|file| {
println!(
"| {0: <16} | {1: <35} | {2: <16} | {3: <32} |",
file.hash.red(),
format_path(&file.path, opts).yellow(),
file_size(&file.path).blue(),
modified_time(&file.path).blue()
);
if parsed_file_indices
.clone()
.into_iter()
.any(|index| index > (duplicates.len() - 1))
{
println!("{}", "Err: File Index Out of Bounds!".red());
return process_group_action(duplicates, dup_index, dup_size, table);
}
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
if parsed_file_indices.is_empty() {
return;
}
let files_to_delete = parsed_file_indices
.into_iter()
.map(|index| duplicates[index].clone());
println!("\n{}", "The following files will be deleted:".red());
files_to_delete
.clone()
.enumerate()
.for_each(|(index, file)| {
println!("{}: {}", index.to_string().blue(), file.path.display());
});
print_divider();
});
match scan_group_confirmation().unwrap() {
true => {
file_manager::delete_files(files_to_delete.collect::<Vec<File>>()).ok();
}
false => println!("{}", "\nCancelled Delete Operation.".red()),
}
}
pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
if duplicates.is_empty() {
println!(
"\n{}",
"No duplicates found matching your search criteria.".green()
);
return;
}
duplicates
.clone()
.into_iter()
.sorted_unstable_by_key(|(_, f)| {
-(f.first().and_then(|ff| ff.size).unwrap_or_default() as i64)
}) // sort by descending file size in interactive mode
.enumerate()
.for_each(|(gindex, (_, group))| {
let mut itable = Table::new();
itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
itable.set_titles(row!["index", "filename", "size", "updated_at"]);
group.iter().enumerate().for_each(|(index, file)| {
itable.add_row(row![
index,
format_path(&file.path, opts).unwrap_or_default().blue(),
file_size(file).unwrap_or_default().red(),
modified_time(&file.path).unwrap_or_default().yellow()
]);
});
process_group_action(&group, gindex, duplicates.len(), itable);
});
}
pub fn print(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
if duplicates.is_empty() {
println!(
"\n{}",
"No duplicates found matching your search criteria.".green()
);
return;
}
let mut output_table = Table::new();
let progress_bar = ProgressBar::new(duplicates.len() as u64);
progress_bar.enable_steady_tick(Duration::from_millis(50));
let progress_style = ProgressStyle::default_bar()
.template("{spinner:.green} [generating output] [{wide_bar:.cyan/blue}] {pos}/{len} files")
.unwrap();
progress_bar.set_style(progress_style);
output_table.set_titles(row!["hash", "duplicates"]);
duplicates
.into_iter()
.sorted_unstable_by_key(|(_, f)| f.first().and_then(|ff| ff.size).unwrap_or_default())
.progress_with(progress_bar)
.for_each(|(hash, group)| {
let mut inner_table = Table::new();
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
group.iter().for_each(|file| {
inner_table.add_row(row![
format_path(&file.path, opts).unwrap_or_default().blue(),
file_size(file).unwrap_or_default().red(),
modified_time(&file.path).unwrap_or_default().yellow()
]);
});
output_table.add_row(row![hash.green(), inner_table]);
});
output_table.printstd();
}
#[allow(unused)]
pub fn raw(duplicates: DashMap<String, Vec<File>>, opts: &Params) -> Result<()> {
if duplicates.is_empty() {
println!(
"\n{}",
"No duplicates found matching your search criteria.".green()
);
return Ok(());
}
duplicates
.into_iter()
.sorted_unstable_by_key(|(_, f)| f.first().and_then(|ff| ff.size).unwrap_or_default())
.for_each(|(_hash, group)| {
group.iter().for_each(|file| {
println!(
"{}\t{}\t{}",
format_path(&file.path, opts).unwrap_or_default().blue(),
file_size(file).unwrap_or_default().red(),
modified_time(&file.path).unwrap_or_default().yellow()
)
});
println!("---");
});
Ok(())
}

View File

@@ -1,40 +1,88 @@
use std::path::PathBuf;
use clap::Parser;
use anyhow::Result;
use std::fs;
use std::{fs, path::PathBuf};
use anyhow::{anyhow, Result};
use clap::{Parser, ValueHint};
use globwalk::{GlobWalker, GlobWalkerBuilder};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Params {
/// Filetypes to deduplicate (default = all)
/// Filetypes to deduplicate [default = all]
#[arg(short, long)]
pub types: Option<String>,
/// Run Deduplicator on dir different from pwd
#[arg(long)]
/// Run Deduplicator on dir different from pwd (e.g., ~/Pictures )
#[arg(value_hint = ValueHint::DirPath, value_name = "scan_dir_path")]
pub dir: Option<PathBuf>,
/// Don't use cache for indexing files (default = true)
/// Delete files interactively
#[arg(long, short)]
pub nocache: bool,
pub interactive: bool,
/// Minimum filesize of duplicates to scan (e.g., 100B/1K/2M/3G/4T).
#[arg(long, short = 's', default_value = "1b")]
pub min_size: Option<String>,
/// Max Depth to scan while looking for duplicates
#[arg(long, short = 'd')]
pub max_depth: Option<usize>,
/// Min Depth to scan while looking for duplicates
#[arg(long)]
pub min_depth: Option<usize>,
/// Follow links while scanning directories
#[arg(long, short)]
pub follow_links: bool,
}
impl Params {
pub fn get_directory(&self) -> Result<String> {
let dir_string: String = self
.dir
.clone()
.unwrap_or(std::env::current_dir()?)
.as_os_str()
.to_str()
.unwrap()
.to_string();
let dir_pathbuf = PathBuf::from(&dir_string);
let dir = fs::canonicalize(&dir_pathbuf)?
.as_os_str()
.to_str()
.unwrap()
.to_string();
pub fn get_min_size(&self) -> Option<u64> {
match &self.min_size {
Some(msize) => match msize.parse::<bytesize::ByteSize>() {
Ok(units) => Some(units.0),
Err(_) => None,
},
None => None,
}
}
pub fn get_directory(&self) -> Result<PathBuf> {
let current_dir = std::env::current_dir()?;
let dir_path = self.dir.as_ref().unwrap_or(&current_dir).as_path();
let dir = fs::canonicalize(dir_path)?;
Ok(dir)
}
fn add_glob_min_depth(&self, builder: GlobWalkerBuilder) -> Result<GlobWalkerBuilder> {
match self.min_depth {
Some(mindepth) => Ok(builder.min_depth(mindepth)),
None => Ok(builder),
}
}
fn add_glob_max_depth(&self, builder: GlobWalkerBuilder) -> Result<GlobWalkerBuilder> {
match self.max_depth {
Some(maxdepth) => Ok(builder.max_depth(maxdepth)),
None => Ok(builder),
}
}
fn add_glob_follow_links(&self, builder: GlobWalkerBuilder) -> Result<GlobWalkerBuilder> {
match self.follow_links {
true => Ok(builder.follow_links(true)),
false => Ok(builder.follow_links(false)),
}
}
pub fn get_glob_walker(&self) -> Result<GlobWalker> {
let pattern: String = match self.types.as_ref() {
Some(filetypes) => format!("**/*{{{filetypes}}}"),
None => "**/*".to_string(),
};
let glob_walker_builder = self
.add_glob_min_depth(GlobWalkerBuilder::from_patterns(
self.get_directory()?,
&[pattern],
))
.and_then(|builder| self.add_glob_max_depth(builder))
.and_then(|builder| self.add_glob_follow_links(builder))?;
glob_walker_builder.build().map_err(|e| anyhow!(e))
}
}

View File

@@ -1,79 +1,151 @@
use crate::database;
use crate::{params::Params, database::File};
use crate::{file_manager::File, filters, params::Params};
use anyhow::Result;
use glob::glob;
use itertools::Itertools;
use dashmap::DashMap;
use fxhash::hash64 as hasher;
use indicatif::{ParallelProgressIterator, ProgressBar, ProgressIterator, ProgressStyle};
use memmap2::Mmap;
use rayon::prelude::*;
use std::fs;
use std::path::PathBuf;
use fxhash::hash32 as hasher;
use std::hash::Hasher;
use std::time::Duration;
use std::{
fs,
path::{Path, PathBuf},
};
pub fn duplicates(app_opts: &Params, connection: &sqlite::Connection) -> Result<Vec<File>> {
let scan_results = scan(app_opts, connection)?;
let base_path = app_opts.get_directory()?;
index_files(scan_results, connection);
database::duplicate_hashes(connection, &base_path)
#[derive(Clone, Copy)]
enum IndexCritera {
Size,
Hash,
}
fn get_glob_patterns(opts: &Params, directory: &String) -> Vec<PathBuf> {
opts.types
.clone()
.unwrap_or(String::from("*"))
.split(",")
.map(|filetype| format!("*.{}", filetype))
.map(|filetype| {
vec![directory.clone(), String::from("**"), filetype]
.iter()
.collect()
})
.collect()
}
pub fn duplicates(app_opts: &Params) -> Result<DashMap<String, Vec<File>>> {
let scan_results = scan(app_opts)?;
let size_index_store = index_files(scan_results, IndexCritera::Size)?;
fn is_indexed_file(path: &String, indexed: &Vec<File>) -> bool {
indexed
.into_iter()
.map(|file| file.path.clone())
.contains(path)
}
fn scan(app_opts: &Params, connection: &sqlite::Connection) -> Result<Vec<String>> {
let directory = app_opts.get_directory()?;
let glob_patterns: Vec<PathBuf> = get_glob_patterns(&app_opts, &directory);
let indexed_paths = database::indexed_paths(connection)?;
let files: Vec<String> = glob_patterns
let sizewize_duplicate_files = size_index_store
.into_par_iter()
.map(|glob_pattern| glob(&glob_pattern.as_os_str().to_str().unwrap()))
.map(|glob_result| glob_result.unwrap())
.flat_map(|file_vec| {
file_vec
.map(|x| x.unwrap().as_os_str().to_str().unwrap().to_string())
.filter(|fpath| !is_indexed_file(fpath, &indexed_paths))
.filter(|glob_result| fs::metadata(glob_result).unwrap().is_file())
.collect::<Vec<String>>()
.filter(|(_, files)| files.len() > 1)
.map(|(_, files)| files)
.flatten()
.collect::<Vec<File>>();
if sizewize_duplicate_files.len() > 1 {
let hash_index_store = index_files(sizewize_duplicate_files, IndexCritera::Hash)?;
let duplicate_files = hash_index_store
.into_par_iter()
.filter(|(_, files)| files.len() > 1)
.collect();
Ok(duplicate_files)
} else {
Ok(DashMap::new())
}
}
fn scan(app_opts: &Params) -> Result<Vec<File>> {
let walker = app_opts.get_glob_walker()?;
let progress = ProgressBar::new_spinner();
let progress_style =
ProgressStyle::with_template("{spinner:.green} [mapping paths] {pos} paths")?;
progress.set_style(progress_style);
progress.enable_steady_tick(Duration::from_millis(50));
let files = walker
.progress_with(progress)
.filter_map(Result::ok)
.map(|file| file.into_path())
.filter(|fpath| fpath.is_file())
.collect::<Vec<PathBuf>>();
let scan_progress = ProgressBar::new(files.len() as u64);
let scan_progress_style = ProgressStyle::with_template(
"{spinner:.green} [processing mapped paths] [{wide_bar:.cyan/blue}] {pos}/{len} files",
)?;
scan_progress.set_style(scan_progress_style);
scan_progress.enable_steady_tick(Duration::from_millis(50));
let scan_results = files
.into_par_iter()
.progress_with(scan_progress)
.map(|fpath| File {
path: fpath.clone(),
hash: None,
size: Some(
fs::metadata(fpath)
.map(|metadata| metadata.len())
.unwrap_or_default(),
),
})
.filter(|file| filters::is_file_gt_min_size(app_opts, file))
.collect();
Ok(files)
Ok(scan_results)
}
fn index_files(files: Vec<String>, connection: &sqlite::Connection) {
let hashed: Vec<File> = files
fn process_file_index(
mut file: File,
store: &DashMap<String, Vec<File>>,
index_criteria: IndexCritera,
) {
match index_criteria {
IndexCritera::Size => {
store
.entry(file.size.unwrap_or_default().to_string())
.and_modify(|fileset| fileset.push(file.clone()))
.or_insert_with(|| vec![file]);
}
IndexCritera::Hash => {
file.hash = Some(hash_file(&file.path).unwrap_or_default());
store
.entry(file.clone().hash.unwrap_or_default())
.and_modify(|fileset| fileset.push(file.clone()))
.or_insert_with(|| vec![file]);
}
}
}
fn index_files(
files: Vec<File>,
index_criteria: IndexCritera,
) -> Result<DashMap<String, Vec<File>>> {
let store: DashMap<String, Vec<File>> = DashMap::new();
let index_progress = ProgressBar::new(files.len() as u64);
let index_progress_style = ProgressStyle::with_template(
"{spinner:.green} [indexing files] [{wide_bar:.cyan/blue}] {pos}/{len} files",
)?;
index_progress.set_style(index_progress_style);
index_progress.enable_steady_tick(Duration::from_millis(50));
files
.into_par_iter()
.map(|file| {
let hash = hash_file(&file).unwrap();
database::File { path: file, hash }
})
.collect();
.progress_with(index_progress)
.for_each(|file| process_file_index(file, &store, index_criteria));
hashed.into_iter().for_each(|file| {
database::put(&file, connection).unwrap();
});
Ok(store)
}
pub fn hash_file(filepath: &str) -> Result<String> {
fn incremental_hashing(filepath: &Path) -> Result<String> {
let file = fs::File::open(filepath)?;
let fmap = unsafe { Mmap::map(&file)? };
let mut inchasher = fxhash::FxHasher::default();
fmap.chunks(1_000_000)
.for_each(|mega| inchasher.write(mega));
Ok(format!("{}", inchasher.finish()))
}
fn standard_hashing(filepath: &Path) -> Result<String> {
let file = fs::read(filepath)?;
let hash = hasher(&*file).to_string();
Ok(hash)
Ok(hasher(&*file).to_string())
}
fn hash_file(filepath: &Path) -> Result<String> {
let filemeta = fs::metadata(filepath)?;
// NOTE: USE INCREMENTAL HASHING ONLY FOR FILES > 100MB
match filemeta.len() < 100_000_000 {
true => standard_hashing(filepath),
false => incremental_hashing(filepath),
}
}