mirror of
https://github.com/sreedevk/deduplicator.git
synced 2026-09-01 21:15:33 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a573fbf3f | ||
|
|
b12b662ba1 | ||
|
|
96337b6b48 | ||
|
|
02f70aa55f | ||
|
|
4a18b3fd91 | ||
|
|
876fdc69ac | ||
|
|
8acc3f8c8f | ||
|
|
38f6c48452 | ||
|
|
3e1e9c0e2e | ||
|
|
0674ab55bf | ||
|
|
ccff95dfc5 | ||
|
|
53d28abb62 | ||
|
|
ea6cf94952 | ||
|
|
7b275d40d0 | ||
|
|
30398c3ca9 |
41
.github/workflows/release.yml
vendored
Normal file
41
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT_NAME: deduplicator
|
||||||
|
PROJECT_DESC: "Filter, Sort & Delete Duplicate Files Recursively"
|
||||||
|
PROJECT_AUTH: "sreedevk"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "[0-9]+.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: taiki-e/create-gh-release-action@v1.6.2
|
||||||
|
with:
|
||||||
|
changelog: CHANGELOG.md
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
upload-assets:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macos-latest
|
||||||
|
- windows-latest
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: taiki-e/upload-rust-binary-action@v1
|
||||||
|
with:
|
||||||
|
bin: deduplicator
|
||||||
|
tar: unix
|
||||||
|
zip: windows
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
20
.github/workflows/rust.yml
vendored
20
.github/workflows/rust.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -305,7 +305,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deduplicator"
|
name = "deduplicator"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
@@ -932,9 +932,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.23.0"
|
version = "1.23.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deduplicator"
|
name = "deduplicator"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "find,filter,delete Duplicates"
|
description = "find,filter,delete Duplicates"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -23,5 +23,5 @@ memmap2 = "0.5.8"
|
|||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
rayon = "1.6.1"
|
rayon = "1.6.1"
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
tokio = { version = "1.23.0", features = ["full"] }
|
tokio = { version = "1.23.1", features = ["full"] }
|
||||||
unicode-segmentation = "1.10.0"
|
unicode-segmentation = "1.10.0"
|
||||||
|
|||||||
@@ -45,7 +45,4 @@ cargo install deduplicator
|
|||||||
|
|
||||||
<h2 align="center">Screenshots</h2>
|
<h2 align="center">Screenshots</h2>
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/36154121/213618143-e5182e39-731e-4817-87dd-1a6a0f38a449.gif" />
|
||||||
<img src="https://user-images.githubusercontent.com/36154121/211948081-63c12b94-6251-487b-a49f-ac5418169d5a.gif" />
|
|
||||||
<img src="https://user-images.githubusercontent.com/36154121/211458077-90092aa3-496c-492f-a061-618059890d5f.png" />
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ fn modified_time(path: &String) -> Result<String> {
|
|||||||
Ok(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_meta_info() {
|
|
||||||
println!("Deduplicator v{}", std::env!("CARGO_PKG_VERSION"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scan_group_instruction() -> Result<String> {
|
fn scan_group_instruction() -> Result<String> {
|
||||||
println!("\nEnter the indices of the files you want to delete.");
|
println!("\nEnter the indices of the files you want to delete.");
|
||||||
println!("You can enter multiple files using commas to seperate file indices.");
|
println!("You can enter multiple files using commas to seperate file indices.");
|
||||||
@@ -115,8 +111,6 @@ fn process_group_action(duplicates: &Vec<File>, dup_index: usize, dup_size: usiz
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
||||||
print_meta_info();
|
|
||||||
|
|
||||||
if duplicates.is_empty() {
|
if duplicates.is_empty() {
|
||||||
println!(
|
println!(
|
||||||
"\n{}",
|
"\n{}",
|
||||||
@@ -128,8 +122,8 @@ pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
|||||||
duplicates
|
duplicates
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_unstable_by_key(|f| {
|
.sorted_unstable_by_key(|(_, f)| {
|
||||||
-(f.1.first().and_then(|ff| ff.size).unwrap_or_default() as i64)
|
-(f.first().and_then(|ff| ff.size).unwrap_or_default() as i64)
|
||||||
}) // sort by descending file size in interactive mode
|
}) // sort by descending file size in interactive mode
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(gindex, (_, group))| {
|
.for_each(|(gindex, (_, group))| {
|
||||||
@@ -140,7 +134,7 @@ pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
|||||||
itable.add_row(row![
|
itable.add_row(row![
|
||||||
index,
|
index,
|
||||||
format_path(&file.path, opts).unwrap_or_default().blue(),
|
format_path(&file.path, opts).unwrap_or_default().blue(),
|
||||||
file_size(&file).unwrap_or_default().red(),
|
file_size(file).unwrap_or_default().red(),
|
||||||
modified_time(&file.path).unwrap_or_default().yellow()
|
modified_time(&file.path).unwrap_or_default().yellow()
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -150,8 +144,6 @@ pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
pub fn print(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
||||||
print_meta_info();
|
|
||||||
|
|
||||||
if duplicates.is_empty() {
|
if duplicates.is_empty() {
|
||||||
println!(
|
println!(
|
||||||
"\n{}",
|
"\n{}",
|
||||||
@@ -164,14 +156,14 @@ pub fn print(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
|
|||||||
output_table.set_titles(row!["hash", "duplicates"]);
|
output_table.set_titles(row!["hash", "duplicates"]);
|
||||||
duplicates
|
duplicates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_unstable_by_key(|f| f.1.first().and_then(|ff| ff.size).unwrap_or_default()) // sort by ascending size
|
.sorted_unstable_by_key(|(_, f)| f.first().and_then(|ff| ff.size).unwrap_or_default())
|
||||||
.for_each(|(hash, group)| {
|
.for_each(|(hash, group)| {
|
||||||
let mut inner_table = Table::new();
|
let mut inner_table = Table::new();
|
||||||
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||||
group.iter().for_each(|file| {
|
group.iter().for_each(|file| {
|
||||||
inner_table.add_row(row![
|
inner_table.add_row(row![
|
||||||
format_path(&file.path, opts).unwrap_or_default().blue(),
|
format_path(&file.path, opts).unwrap_or_default().blue(),
|
||||||
file_size(&file).unwrap_or_default().red(),
|
file_size(file).unwrap_or_default().red(),
|
||||||
modified_time(&file.path).unwrap_or_default().yellow()
|
modified_time(&file.path).unwrap_or_default().yellow()
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ impl Params {
|
|||||||
pub fn get_directory(&self) -> Result<String> {
|
pub fn get_directory(&self) -> Result<String> {
|
||||||
let dir_pathbuf: PathBuf = self
|
let dir_pathbuf: PathBuf = self
|
||||||
.dir
|
.dir
|
||||||
.clone()
|
.as_ref()
|
||||||
.unwrap_or(std::env::current_dir()?)
|
.unwrap_or(&std::env::current_dir()?)
|
||||||
.as_os_str()
|
.as_os_str()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@@ -47,17 +47,18 @@ impl Params {
|
|||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_glob_patterns(&self) -> Vec<PathBuf> {
|
pub fn get_glob_patterns(&self) -> PathBuf {
|
||||||
self.types
|
match self.types.as_ref() {
|
||||||
.clone()
|
Some(filetypes) => vec![
|
||||||
.unwrap_or_else(|| String::from("*"))
|
self.get_directory().unwrap(),
|
||||||
.split(',')
|
String::from("**"),
|
||||||
.map(|filetype| format!("*.{}", filetype))
|
format!("{{{}}}", filetypes),
|
||||||
.map(|filetype| {
|
]
|
||||||
vec![self.get_directory().unwrap(), String::from("**"), filetype]
|
.iter()
|
||||||
.iter()
|
.collect::<PathBuf>(),
|
||||||
.collect()
|
None => vec![self.get_directory().unwrap().as_str(), "**", "*"]
|
||||||
})
|
.iter()
|
||||||
.collect()
|
.collect::<PathBuf>(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,27 +40,22 @@ pub fn duplicates(app_opts: &Params) -> Result<DashMap<String, Vec<File>>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scan(app_opts: &Params) -> Result<Vec<File>> {
|
fn scan(app_opts: &Params) -> Result<Vec<File>> {
|
||||||
let glob_patterns: Vec<PathBuf> = app_opts.get_glob_patterns();
|
let glob_patterns = app_opts.get_glob_patterns().display().to_string();
|
||||||
let files: Vec<File> = glob_patterns
|
let glob_iter = glob(&glob_patterns)?;
|
||||||
.par_iter()
|
let files = glob_iter
|
||||||
|
.filter(Result::is_ok)
|
||||||
|
.map(|file| file.unwrap())
|
||||||
|
.filter(|fpath| fpath.is_file())
|
||||||
|
.collect::<Vec<PathBuf>>()
|
||||||
|
.into_par_iter()
|
||||||
.progress_with_style(ProgressStyle::with_template(
|
.progress_with_style(ProgressStyle::with_template(
|
||||||
"{spinner:.green} [scanning files] [{wide_bar:.cyan/blue}] {pos}/{len} files",
|
"{spinner:.green} [processing scan results] [{wide_bar:.cyan/blue}] {pos}/{len} files",
|
||||||
)?)
|
)?)
|
||||||
.filter_map(|glob_pattern| glob(glob_pattern.as_os_str().to_str()?).ok())
|
.map(|fpath| fpath.display().to_string())
|
||||||
.flat_map(|file_vec| {
|
.map(|fpath| File {
|
||||||
file_vec
|
path: fpath.clone(),
|
||||||
.filter_map(|x| Some(x.ok()?.as_os_str().to_str()?.to_string()))
|
|
||||||
.filter(|glob_result| {
|
|
||||||
fs::metadata(glob_result)
|
|
||||||
.map(|f| f.is_file())
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
})
|
|
||||||
.map(|file_path| File {
|
|
||||||
path: file_path.clone(),
|
|
||||||
hash: None,
|
hash: None,
|
||||||
size: Some(fs::metadata(file_path).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_minsize(app_opts, file))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -68,16 +63,8 @@ fn scan(app_opts: &Params) -> Result<Vec<File>> {
|
|||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_file_hash_index(file: &File) -> Result<File> {
|
|
||||||
Ok(File {
|
|
||||||
path: file.path.clone(),
|
|
||||||
size: file.size,
|
|
||||||
hash: Some(hash_file(&file.path).unwrap_or_default()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_file_index(
|
fn process_file_index(
|
||||||
file: File,
|
mut file: File,
|
||||||
store: &DashMap<String, Vec<File>>,
|
store: &DashMap<String, Vec<File>>,
|
||||||
index_criteria: IndexCritera,
|
index_criteria: IndexCritera,
|
||||||
) {
|
) {
|
||||||
@@ -89,13 +76,11 @@ fn process_file_index(
|
|||||||
.or_insert_with(|| vec![file]);
|
.or_insert_with(|| vec![file]);
|
||||||
}
|
}
|
||||||
IndexCritera::Hash => {
|
IndexCritera::Hash => {
|
||||||
let processed_file = process_file_hash_index(&file).unwrap();
|
file.hash = Some(hash_file(&file.path).unwrap_or_default());
|
||||||
let indexhash = processed_file.clone().hash.unwrap_or_default();
|
|
||||||
|
|
||||||
store
|
store
|
||||||
.entry(indexhash)
|
.entry(file.clone().hash.unwrap())
|
||||||
.and_modify(|fileset| fileset.push(processed_file.clone()))
|
.and_modify(|fileset| fileset.push(file.clone()))
|
||||||
.or_insert_with(|| vec![processed_file]);
|
.or_insert_with(|| vec![file]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user