mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
101 lines
2.4 KiB
Rust
101 lines
2.4 KiB
Rust
use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}};
|
|
|
|
use tokio::task::JoinHandle;
|
|
use yazi_shared::Either;
|
|
|
|
type Task = Either<PathBuf, std::fs::ReadDir>;
|
|
|
|
pub enum SizeCalculator {
|
|
Idle((VecDeque<Task>, Option<u64>)),
|
|
Pending(JoinHandle<(VecDeque<Task>, Option<u64>)>),
|
|
}
|
|
|
|
impl SizeCalculator {
|
|
pub async fn new(path: &Path) -> io::Result<Self> {
|
|
let p = path.to_owned();
|
|
tokio::task::spawn_blocking(move || {
|
|
let meta = std::fs::symlink_metadata(&p)?;
|
|
if !meta.is_dir() {
|
|
return Ok(Self::Idle((VecDeque::new(), Some(meta.len()))));
|
|
}
|
|
|
|
let mut buf = VecDeque::from([Either::Right(std::fs::read_dir(&p)?)]);
|
|
let size = Self::next_chunk(&mut buf);
|
|
Ok(Self::Idle((buf, size)))
|
|
})
|
|
.await?
|
|
}
|
|
|
|
pub async fn total(path: &Path) -> io::Result<u64> {
|
|
let mut it = Self::new(path).await?;
|
|
let mut total = 0;
|
|
while let Some(n) = it.next().await? {
|
|
total += n;
|
|
}
|
|
Ok(total)
|
|
}
|
|
|
|
pub async fn next(&mut self) -> io::Result<Option<u64>> {
|
|
poll_fn(|cx| {
|
|
loop {
|
|
match self {
|
|
Self::Idle((buf, size)) => {
|
|
if let Some(s) = size.take() {
|
|
return Poll::Ready(Ok(Some(s)));
|
|
} else if buf.is_empty() {
|
|
return Poll::Ready(Ok(None));
|
|
}
|
|
|
|
let mut buf = mem::take(buf);
|
|
*self = Self::Pending(tokio::task::spawn_blocking(move || {
|
|
let size = Self::next_chunk(&mut buf);
|
|
(buf, size)
|
|
}));
|
|
}
|
|
Self::Pending(handle) => {
|
|
*self = Self::Idle(ready!(Pin::new(handle).poll(cx))?);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.await
|
|
}
|
|
|
|
fn next_chunk(buf: &mut VecDeque<Either<PathBuf, std::fs::ReadDir>>) -> Option<u64> {
|
|
let (mut i, mut size, now) = (0, 0, Instant::now());
|
|
macro_rules! pop_and_continue {
|
|
() => {{
|
|
buf.pop_front();
|
|
if buf.is_empty() {
|
|
return Some(size);
|
|
}
|
|
continue;
|
|
}};
|
|
}
|
|
|
|
while i < 5000 && now.elapsed() < Duration::from_millis(50) {
|
|
i += 1;
|
|
let front = buf.front_mut()?;
|
|
|
|
if let Either::Left(p) = front {
|
|
*front = match std::fs::read_dir(p) {
|
|
Ok(it) => Either::Right(it),
|
|
Err(_) => pop_and_continue!(),
|
|
};
|
|
}
|
|
|
|
let Some(next) = front.right_mut()?.next() else {
|
|
pop_and_continue!();
|
|
};
|
|
|
|
let Ok(ent) = next else { continue };
|
|
let Ok(ft) = ent.file_type() else { continue };
|
|
if ft.is_dir() {
|
|
buf.push_back(Either::Left(ent.path()));
|
|
} else if let Ok(meta) = ent.metadata() {
|
|
size += meta.len();
|
|
}
|
|
}
|
|
Some(size)
|
|
}
|
|
}
|