You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
6.5 KiB
243 lines
6.5 KiB
use super::*;
|
|
use std::{
|
|
path::{
|
|
Path
|
|
},
|
|
fs::{
|
|
self,
|
|
OpenOptions,
|
|
},
|
|
convert::{
|
|
TryInto,
|
|
},
|
|
ops::{
|
|
self,
|
|
},
|
|
};
|
|
|
|
/// Handle a detected dupe
|
|
fn handle_dupe<P>(path: P, mode: &config::Mode) -> Result<(), error::Error>
|
|
where P: AsRef<Path>
|
|
{
|
|
log!(Info, mode.logging_mode => " -> {:?}", path.as_ref());
|
|
match mode.operation_mode
|
|
{
|
|
config::OperationMode::Delete => {
|
|
mode.error_mode.handle(std::fs::remove_file(path.as_ref()))?;
|
|
},
|
|
_ => (),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle a detected dupe async
|
|
#[inline(always)]
|
|
#[cfg(feature="threads")]
|
|
async fn handle_dupe_async<P>(path: P, mode: &config::Mode) -> Result<(), error::Error>
|
|
where P: AsRef<Path>
|
|
{
|
|
log!(Info, mode.logging_mode => " -> {:?}", path.as_ref());
|
|
match mode.operation_mode
|
|
{
|
|
config::OperationMode::Delete => {
|
|
mode.error_mode.handle(tokio::fs::remove_file(path.as_ref()).await)?;
|
|
},
|
|
_ => (),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub struct DupeCount
|
|
{
|
|
pub total: usize,
|
|
pub dupes: usize,
|
|
}
|
|
|
|
impl From<bool> for DupeCount {
|
|
fn from(b: bool) -> Self
|
|
{
|
|
Self{
|
|
total: 1,
|
|
dupes: if b {0} else {1},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ops::Add for DupeCount
|
|
{
|
|
type Output = Self;
|
|
fn add(self, other: Self) -> Self
|
|
{
|
|
Self {
|
|
total: self.total + other.total,
|
|
dupes: self.dupes + other.dupes,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ops::AddAssign for DupeCount
|
|
{
|
|
fn add_assign(&mut self, other: Self)
|
|
{
|
|
*self = Self {
|
|
total: self.total + other.total,
|
|
dupes: self.dupes + other.dupes,
|
|
};
|
|
}
|
|
}
|
|
|
|
impl Default for DupeCount
|
|
{
|
|
fn default() -> Self
|
|
{
|
|
Self{total:0, dupes:0}
|
|
}
|
|
}
|
|
|
|
/// Process a file and add it to the table, returns true if is not a dupe.
|
|
pub fn process_file<P: AsRef<Path>>(file: P, set: &mut container::DupeMap) -> Result<bool, error::Error>
|
|
{
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.open(file)?;
|
|
let sz: usize = file.metadata()?.len().try_into().or(Err(error::Error::Arch(Some("Filesize is too large to be known. you have likely compiled the binary for 32-bit architecture or less. This shouldn't happen on 64-bit systems."))))?;
|
|
|
|
let mut result = hash::Sha256Hash::default();
|
|
error::check_size(sz, hash::compute(&mut file, &mut result)?)?;
|
|
|
|
Ok(set.try_add(result))
|
|
}
|
|
|
|
|
|
/// Process a file and add it to the table, returns true if is not a dupe.
|
|
#[cfg(feature="threads")]
|
|
pub async fn process_file_async<P: AsRef<Path>>(file: P, set: &std::sync::Arc<tokio::sync::Mutex<container::DupeMap>>) -> Result<bool, error::Error>
|
|
{
|
|
use tokio::{
|
|
fs::{
|
|
OpenOptions,
|
|
},
|
|
};
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.open(file).await?;
|
|
let sz: usize = file.metadata().await?.len().try_into().or(Err(error::Error::Arch(Some("Filesize is too large to be known. you have likely compiled the binary for 32-bit architecture or less. This shouldn't happen on 64-bit systems."))))?;
|
|
|
|
let mut result = hash::Sha256Hash::default();
|
|
error::check_size(sz, hash::compute_async(&mut file, &mut result).await?)?;
|
|
|
|
let mut set = set.lock().await;
|
|
Ok(set.try_add(result))
|
|
}
|
|
|
|
/// Walk a dir structure and remove all dupes in it
|
|
pub fn do_dir<P: AsRef<Path>>(dir: P, depth: usize, set: &mut container::DupeMap, mode: &config::Mode) -> Result<DupeCount, error::Error>
|
|
{
|
|
let recurse = match mode.recursion_mode {
|
|
config::RecursionMode::N(n) if n > depth => true,
|
|
config::RecursionMode::All => true,
|
|
_ => false,
|
|
};
|
|
let cmode = mode;
|
|
let mode = &mode.error_mode;
|
|
let mut count = DupeCount::default();
|
|
|
|
for obj in fs::read_dir(dir.as_ref())? //always return error if this fails
|
|
{
|
|
if let Some(obj) = mode.handle(obj)? { // Each one is allowed to fail if `mode` says so
|
|
let obj = obj.path();
|
|
|
|
if obj.is_dir() && recurse {
|
|
count += mode.handle(do_dir(obj, depth+1, set, cmode))?.unwrap_or_default();
|
|
} else {
|
|
count += if mode.handle(process_file(&obj, set))?.unwrap_or_default() {
|
|
log!(Info, cmode.logging_mode => "OK {:?}", obj);
|
|
DupeCount{total: 1, dupes: 0}
|
|
} else {
|
|
mode.handle(handle_dupe(obj, &cmode))?;
|
|
DupeCount{total: 1, dupes: 1}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(count)
|
|
}
|
|
|
|
/// Walk a dir structure and remove all dupes in it
|
|
#[cfg(feature="threads")]
|
|
pub fn do_dir_async<P: AsRef<Path> + std::marker::Send + std::marker::Sync + 'static>(dir: P, depth: usize, set: std::sync::Arc<tokio::sync::Mutex<container::DupeMap>>, mode: config::Mode) -> futures::future::BoxFuture<'static, Result<DupeCount, error::Error>>
|
|
{
|
|
use std::sync::Arc;
|
|
use futures::future::{
|
|
FutureExt
|
|
};
|
|
async move {
|
|
let recurse = match mode.recursion_mode {
|
|
config::RecursionMode::N(n) if n > depth => true,
|
|
config::RecursionMode::All => true,
|
|
_ => false,
|
|
};
|
|
let cmode = mode;
|
|
let mode = &cmode.error_mode;
|
|
let mut children = Vec::new();
|
|
let mut workers = Vec::new();
|
|
|
|
let mut dir = tokio::fs::read_dir(dir.as_ref()).await?; //always return error if this fails
|
|
|
|
while let Some(Some(obj)) = mode.handle(dir.next_entry().await)?
|
|
{
|
|
let obj = obj.path();
|
|
|
|
if obj.is_dir() && recurse {
|
|
let set = Arc::clone(&set);
|
|
let cmode = cmode.clone();
|
|
let mode = mode.clone();
|
|
children.push(tokio::task::spawn(async move {
|
|
log!(Info, cmode.logging_mode => "OK {:?}", obj);
|
|
match mode.handle(do_dir_async(obj, depth+1, set, cmode).await) {
|
|
Ok(v) => Ok(v.unwrap_or_default()),
|
|
Err(v) => Err(v),
|
|
}
|
|
}));
|
|
} else {
|
|
let set = Arc::clone(&set);
|
|
let mode = mode.clone();
|
|
let cmode = cmode.clone();
|
|
workers.push(tokio::task::spawn(async move {
|
|
match mode.handle(process_file_async(&obj, &set).await) {
|
|
Ok(v) => {
|
|
if v.unwrap_or_default() {
|
|
log!(Info, cmode.logging_mode => "OK {:?}", obj);
|
|
Ok(true)
|
|
} else {
|
|
if let Err(e) = mode.handle(handle_dupe_async(obj, &cmode).await) {
|
|
Err(e)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
},
|
|
Err(v) => Err(v),
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
|
|
async fn wait_on<T: IntoIterator<Item=tokio::task::JoinHandle<Result<U, error::Error>>>, U: Default+Into<DupeCount>>(children: T, mode: &error::Mode) -> Result<DupeCount, error::Error>
|
|
{
|
|
let mut count = DupeCount::default();
|
|
for child in children.into_iter() {
|
|
count += mode.handle(error::internal(child.await)? /* thread panicked */)?.unwrap_or_default().into();
|
|
}
|
|
Ok(count)
|
|
}
|
|
|
|
// Wait for all children to complete before error checking.
|
|
let er1 = wait_on(workers, &mode).await;
|
|
let er2 = wait_on(children, &mode).await;
|
|
Ok(er1? + er2?)
|
|
}.boxed()
|
|
}
|