parent
2becd03c6d
commit
9d04491637
@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
*~
|
*~
|
||||||
|
/test
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
use std::{
|
||||||
|
path::{
|
||||||
|
Path,
|
||||||
|
PathBuf,
|
||||||
|
},
|
||||||
|
sync::Arc,
|
||||||
|
num::NonZeroUsize,
|
||||||
|
marker::{
|
||||||
|
Send,
|
||||||
|
Sync,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
mpsc,
|
||||||
|
Semaphore,
|
||||||
|
},
|
||||||
|
io,
|
||||||
|
fs,
|
||||||
|
task,
|
||||||
|
stream::StreamExt,
|
||||||
|
};
|
||||||
|
use futures::future::{BoxFuture, join_all, join, FutureExt as _};
|
||||||
|
|
||||||
|
const MAX_PATHS: usize = 10000; //prevent OOM
|
||||||
|
const MAX_WALKERS: usize = 24;
|
||||||
|
|
||||||
|
#[inline] pub fn recommended_max_walkers() -> Option<NonZeroUsize>
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
Some(NonZeroUsize::new_unchecked(MAX_WALKERS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn walk<P: AsRef<Path>+Send+Sync>(path: P, recurse: Option<NonZeroUsize>, max_walkers: Option<NonZeroUsize>) -> Result<Vec<PathBuf>, Error>
|
||||||
|
{
|
||||||
|
let (tx, rx) = mpsc::channel(max_walkers.as_ref().map(|&num| usize::from(num)).unwrap_or(16));
|
||||||
|
let semaphore = max_walkers.map(|x| Arc::new(Semaphore::new(usize::from(x))));
|
||||||
|
|
||||||
|
let (out, sz) = join(rx
|
||||||
|
.take(MAX_PATHS)
|
||||||
|
.collect::<Vec<PathBuf>>(),
|
||||||
|
_walk(path, 1, recurse, semaphore, tx))
|
||||||
|
.await;
|
||||||
|
sz?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline] fn __walk<'a, P: AsRef<Path>+Send+Sync+'a>(path: P, depth: usize, recurse: Option<NonZeroUsize>, semaphore: Option<Arc<Semaphore>>, output: mpsc::Sender<PathBuf>) -> BoxFuture<'a,Result<usize, Error>>
|
||||||
|
{
|
||||||
|
async move {_walk(path,depth,recurse,semaphore,output).await}.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _walk<P: AsRef<Path>+Send+Sync>(path: P, depth: usize, recurse: Option<NonZeroUsize>, semaphore: Option<Arc<Semaphore>>, mut output: mpsc::Sender<PathBuf>) -> Result<usize, Error>
|
||||||
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
let can_recurse = || match &recurse {
|
||||||
|
None => true,
|
||||||
|
&Some(nzu) => depth < usize::from(nzu),
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
let _lock = semaphore.as_ref().map(|x| x.acquire());
|
||||||
|
let mut children = Vec::new();
|
||||||
|
let mut dir = fs::read_dir(path).await?;
|
||||||
|
let mut files=0usize;
|
||||||
|
while let Some(edir) = dir.next_entry().await? {
|
||||||
|
let dir = edir.path();
|
||||||
|
if dir.is_file() {
|
||||||
|
output.send(dir).await?;
|
||||||
|
files+=1;
|
||||||
|
} else if dir.is_dir() && can_recurse() {
|
||||||
|
let sem = semaphore.clone();
|
||||||
|
let output = output.clone();
|
||||||
|
children.push({
|
||||||
|
let child = tokio::spawn(async move {
|
||||||
|
(__walk(&dir, depth+1, recurse, sem, output).await, dir)
|
||||||
|
}.boxed());
|
||||||
|
task::yield_now().await;
|
||||||
|
child
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(join_all(children).await.into_iter()
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(err)=> {
|
||||||
|
eprintln!("Child panic: {}", err);
|
||||||
|
None }
|
||||||
|
})
|
||||||
|
.filter_map(|(x, name)| {
|
||||||
|
match x {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to parse path {:?}: {}", name, e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}).sum::<usize>() + files)
|
||||||
|
} else if path.is_file() {
|
||||||
|
output.send(path.to_owned()).await?;
|
||||||
|
Ok(1)
|
||||||
|
} else {
|
||||||
|
Err(Error::FileNotFound(path.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
IO(io::Error),
|
||||||
|
Send,
|
||||||
|
FileNotFound(PathBuf),
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)>
|
||||||
|
{
|
||||||
|
Some(match &self {
|
||||||
|
Self::IO(io) => io,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::IO(io) => write!(f, "i/o error: {}", io),
|
||||||
|
Self::Send => write!(f, "mpsc error: this usually means we tried to take too many files"),
|
||||||
|
Self::FileNotFound(path) => write!(f, "path {:?} does not exist", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error
|
||||||
|
{
|
||||||
|
fn from(from: io::Error) -> Self
|
||||||
|
{
|
||||||
|
Self::IO(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<mpsc::error::SendError<T>> for Error
|
||||||
|
{
|
||||||
|
#[inline] fn from(_: mpsc::error::SendError<T>) -> Self
|
||||||
|
{
|
||||||
|
Self::Send
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{
|
||||||
|
LinkedList,
|
||||||
|
linked_list::{
|
||||||
|
IntoIter,
|
||||||
|
Iter,
|
||||||
|
IterMut,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
iter::Rev,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct FixedStack<T>(LinkedList<T>, usize);
|
||||||
|
|
||||||
|
impl<T> FixedStack<T>
|
||||||
|
{
|
||||||
|
#[inline] pub fn new(sz: usize) -> Self
|
||||||
|
{
|
||||||
|
Self (
|
||||||
|
LinkedList::new(),
|
||||||
|
sz
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to push an item, returns Ok(()) if successful
|
||||||
|
pub fn push(&mut self, item: T)
|
||||||
|
{
|
||||||
|
if self.0.len() >= self.1 {
|
||||||
|
self.0.pop_back();
|
||||||
|
}
|
||||||
|
self.0.push_front(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self)
|
||||||
|
{
|
||||||
|
self.0.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<T>
|
||||||
|
{
|
||||||
|
self.0.pop_back()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize
|
||||||
|
{
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cap(&self) -> usize
|
||||||
|
{
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> Rev<Iter<T>>
|
||||||
|
{
|
||||||
|
self.0.iter().rev()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self)-> Rev<IterMut<T>>
|
||||||
|
{
|
||||||
|
self.0.iter_mut().rev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for FixedStack<T>
|
||||||
|
{
|
||||||
|
type Item=T;
|
||||||
|
type IntoIter = Rev<IntoIter<T>>;
|
||||||
|
|
||||||
|
fn into_iter(self)-> Self::IntoIter
|
||||||
|
{
|
||||||
|
self.0.into_iter().rev()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue