mirror of
https://github.com/sreedevk/deduplicator.git
synced 2026-09-05 23:10:45 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab44d1ed04 | ||
|
|
6b06798e8e | ||
|
|
4d27da99f3 | ||
|
|
91dce29bb4 | ||
|
|
f9b6d57968 | ||
|
|
5c86a080c4 | ||
|
|
9f4d9139b6 | ||
|
|
153109ef57 | ||
|
|
fa12f85b6a | ||
|
|
7d66aeef6e |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -324,7 +324,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deduplicator"
|
name = "deduplicator"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deduplicator"
|
name = "deduplicator"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "find,filter,delete Duplicates"
|
description = "find,filter,delete Duplicates"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -7,15 +7,38 @@
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```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:
|
Options:
|
||||||
-t, --types <TYPES> Filetypes to deduplicate (default = all)
|
-t, --types <TYPES> Filetypes to deduplicate [default = all]
|
||||||
--dir <DIR> Run Deduplicator on dir different from pwd
|
-i, --interactive Delete files interactively
|
||||||
-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]
|
||||||
-m, --minsize <MINSIZE> Minimum filesize of duplicates to scan (e.g., 100B/1K/2M/3G/4T). [default = 0]
|
-d, --max-depth <MAX_DEPTH> Max Depth to scan while looking for duplicates
|
||||||
-h, --help Print help information
|
--min-depth <MIN_DEPTH> Min Depth to scan while looking for duplicates
|
||||||
-V, --version Print version information
|
-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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -75,7 +98,7 @@ Deduplicator uses size comparison and fxhash (a non non-cryptographic hashing al
|
|||||||
|:---|:---|---:|---:|---:|---:|
|
|:---|:---|---:|---:|---:|---:|
|
||||||
| `deduplicator --dir ~/Data/tmp` | (~120G) | 27.5 ± 1.0 | 26.0 | 32.1 | 1.70 ± 0.09 |
|
| `deduplicator --dir ~/Data/tmp` | (~120G) | 27.5 ± 1.0 | 26.0 | 32.1 | 1.70 ± 0.09 |
|
||||||
| `deduplicator --dir ~/Data/books` | (~8.6G) | 21.8 ± 0.7 | 20.5 | 24.4 | 1.35 ± 0.07 |
|
| `deduplicator --dir ~/Data/books` | (~8.6G) | 21.8 ± 0.7 | 20.5 | 24.4 | 1.35 ± 0.07 |
|
||||||
| `deduplicator --dir ~/Data/books --minsize 10M` | (~8.6G) | 16.1 ± 0.6 | 14.9 | 18.8 | 1.00 |
|
| `deduplicator --dir ~/Data/books --min-size 10M` | (~8.6G) | 16.1 ± 0.6 | 14.9 | 18.8 | 1.00 |
|
||||||
| `deduplicator --dir ~/Data/ --types pdf,jpg,png,jpeg` | (~290G) | 1857.4 ± 24.5 | 1817.0 | 1895.5 | 115.07 ± 4.64 |
|
| `deduplicator --dir ~/Data/ --types pdf,jpg,png,jpeg` | (~290G) | 1857.4 ± 24.5 | 1817.0 | 1895.5 | 115.07 ± 4.64 |
|
||||||
|
|
||||||
* 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.
|
* 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.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::file_manager::File;
|
use crate::file_manager::File;
|
||||||
use crate::params::Params;
|
use crate::params::Params;
|
||||||
|
|
||||||
pub fn is_file_gt_minsize(app_opts: &Params, file: &File) -> bool {
|
pub fn is_file_gt_min_size(app_opts: &Params, file: &File) -> bool {
|
||||||
match app_opts.get_minsize() {
|
match app_opts.get_min_size() {
|
||||||
Some(msize) => match file.size {
|
Some(msize) => match file.size {
|
||||||
Some(fsize) => fsize >= msize,
|
Some(fsize) => fsize >= msize,
|
||||||
None => true,
|
None => true,
|
||||||
|
|||||||
@@ -7,23 +7,32 @@ use globwalk::{GlobWalker, GlobWalkerBuilder};
|
|||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Params {
|
pub struct Params {
|
||||||
/// Filetypes to deduplicate (default = all)
|
/// Filetypes to deduplicate [default = all]
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub types: Option<String>,
|
pub types: Option<String>,
|
||||||
/// Run Deduplicator on dir different from pwd
|
/// Run Deduplicator on dir different from pwd (e.g., ~/Pictures )
|
||||||
#[arg(long, value_hint = ValueHint::DirPath)]
|
#[arg(value_hint = ValueHint::DirPath, value_name = "scan_dir_path")]
|
||||||
pub dir: Option<PathBuf>,
|
pub dir: Option<PathBuf>,
|
||||||
/// Delete files interactively
|
/// Delete files interactively
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
pub interactive: bool,
|
pub interactive: bool,
|
||||||
/// Minimum filesize of duplicates to scan (e.g., 100B/1K/2M/3G/4T). [default = 0]
|
/// 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)]
|
#[arg(long, short)]
|
||||||
pub minsize: Option<String>,
|
pub follow_links: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Params {
|
impl Params {
|
||||||
pub fn get_minsize(&self) -> Option<u64> {
|
pub fn get_min_size(&self) -> Option<u64> {
|
||||||
match &self.minsize {
|
match &self.min_size {
|
||||||
Some(msize) => match msize.parse::<bytesize::ByteSize>() {
|
Some(msize) => match msize.parse::<bytesize::ByteSize>() {
|
||||||
Ok(units) => Some(units.0),
|
Ok(units) => Some(units.0),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
@@ -39,14 +48,41 @@ impl Params {
|
|||||||
Ok(dir)
|
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> {
|
pub fn get_glob_walker(&self) -> Result<GlobWalker> {
|
||||||
let pattern: String = match self.types.as_ref() {
|
let pattern: String = match self.types.as_ref() {
|
||||||
Some(filetypes) => format!("**/*{{{filetypes}}}"),
|
Some(filetypes) => format!("**/*{{{filetypes}}}"),
|
||||||
None => "**/*".to_string(),
|
None => "**/*".to_string(),
|
||||||
};
|
};
|
||||||
// TODO: add params for maximum depth and following symlinks, then pass them to this builder
|
|
||||||
GlobWalkerBuilder::from_patterns(self.get_directory()?, &[pattern])
|
let glob_walker_builder = self
|
||||||
.build()
|
.add_glob_min_depth(GlobWalkerBuilder::from_patterns(
|
||||||
.map_err(|e| anyhow!(e))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ fn scan(app_opts: &Params) -> Result<Vec<File>> {
|
|||||||
hash: None,
|
hash: None,
|
||||||
size: Some(fs::metadata(fpath).unwrap().len()),
|
size: Some(fs::metadata(fpath).unwrap().len()),
|
||||||
})
|
})
|
||||||
.filter(|file| filters::is_file_gt_minsize(app_opts, file))
|
.filter(|file| filters::is_file_gt_min_size(app_opts, file))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(files)
|
Ok(files)
|
||||||
|
|||||||
Reference in New Issue
Block a user