use crate::task_sync::TaskLifetime;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::ops::AddAssign;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::Instant;
use std::thread;
use crossbeam::channel::unbounded;
use crossbeam::sync::WaitGroup;
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::time::Duration;
const SIZE_NAMES: &[&str] = &["", "K", "M", "G", "T", "P", "E", "Z", "Y"];
#[derive(Debug, Copy, Clone)]
pub struct Size(u64);
impl Size {
pub fn from_file(path: &Path) -> Size {
Size(match fs::metadata(path) {
Ok(m) => m.len(),
Err(_) => 0,
})
}
pub fn from_dir(path: &Path, tl: &TaskLifetime) -> Option<Size> {
lazy_static! {
static ref SIZE_CACHE_MUTEX: Mutex<HashMap<PathBuf, Size>> = Mutex::new(HashMap::new());
}
let mut size_cache = SIZE_CACHE_MUTEX.lock().unwrap();
if let Some(s) = size_cache.get(path) {
return Some(*s);
}
let start = Instant::now();
let inodes = Arc::new(Mutex::new(HashSet::<u64>::new())); let size = Arc::new(AtomicUsize::new(0));
let (dirs_sender, dirs_receiver) = unbounded();
let busy = Arc::new(AtomicIsize::new(0));
busy.fetch_add(1, Ordering::Relaxed);
dirs_sender.send(Some(PathBuf::from(path))).unwrap();
let wg = WaitGroup::new();
let period = Duration::from_micros(50);
for _ in 0..8 {
let size = Arc::clone(&size);
let busy = Arc::clone(&busy);
let wg = wg.clone();
let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone());
let tl = tl.clone();
let inodes = inodes.clone();
thread::spawn(move || {
loop {
let o = dirs_receiver.recv_timeout(period);
if let Ok(Some(open_dir)) = o {
if let Ok(entries) = fs::read_dir(&open_dir) {
for e in entries.flatten() {
if let Ok(md) = e.metadata() {
if md.is_dir() {
busy.fetch_add(1, Ordering::Relaxed);
dirs_sender.send(Some(e.path())).unwrap();
} else if md.nlink() > 1 {
let mut inodes = inodes.lock().unwrap();
if !inodes.insert(md.ino()) {
continue; }
}
size.fetch_add(md.len() as usize, Ordering::Relaxed);
}
}
}
busy.fetch_sub(1, Ordering::Relaxed);
dirs_sender.send(None).unwrap();
} else if busy.load(Ordering::Relaxed) < 1 {
break;
}
if tl.is_expired() {
break;
}
}
drop(wg);
});
}
wg.wait();
if tl.is_expired() {
return None;
}
let size: usize = size.load(Ordering::Relaxed);
let size: u64 = size as u64;
let s = Size::from(size);
size_cache.insert(PathBuf::from(path), s);
debug!("size computation for {:?} took {:?}", path, start.elapsed());
Some(s)
}
pub fn to_string(self) -> String {
let mut v = self.0;
let mut i = 0;
while v >= 9000 && i < SIZE_NAMES.len() - 1 {
v >>= 10;
i += 1;
}
format!("{}{}", v, &SIZE_NAMES[i])
}
pub fn discrete_ratio(self, max: Size, r: u64) -> u64 {
if max.0 == 0 || self.0 == 0 {
0
} else {
((r as f64) * (self.0 as f64).cbrt() / (max.0 as f64).cbrt()).round() as u64
}
}
}
impl From<u64> for Size {
fn from(s: u64) -> Size {
Size(s)
}
}
impl AddAssign for Size {
fn add_assign(&mut self, other: Size) {
*self = Size(self.0 + other.0);
}
}
impl Into<u64> for Size {
fn into(self) -> u64 {
self.0
}
}