use std::time::Instant;
use crate::directory::DirectoryStreamState;
use crate::directory::ListOptions;
use crate::directory::ListScope;
use crate::directory::directory_entry_validation;
use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
use crate::metadata::DirEntry;
use crate::metadata::FileSystemLimits;
pub(crate) struct ListStreamPolicy {
scope: ListScope,
options: ListOptions,
provider: Box<str>,
path_semantics: crate::path::PathSemantics,
limits: FileSystemLimits,
state: DirectoryStreamState,
deadline: Option<Instant>,
returned_entries: usize,
}
impl ListStreamPolicy {
pub(crate) fn new(
scope: ListScope,
options: ListOptions,
provider: &str,
path_semantics: crate::path::PathSemantics,
limits: FileSystemLimits,
now: Instant,
) -> FsResult<Self> {
let mut policy = Self {
scope,
options,
provider: provider.into(),
path_semantics,
limits,
state: DirectoryStreamState::Open,
deadline: None,
returned_entries: 0,
};
policy.deadline = policy
.options
.deadline()
.map(|duration| {
now.checked_add(duration).ok_or_else(|| {
policy.contextual_error(FsError::new(
FsErrorKind::InvalidOptions,
FsOperation::List,
"list deadline exceeds the platform monotonic-clock range",
))
})
})
.transpose()?;
Ok(policy)
}
pub(crate) const fn state(&self) -> DirectoryStreamState {
self.state
}
pub(crate) fn before_next(&mut self, now: Instant) -> FsResult<()> {
if self.state != DirectoryStreamState::Open {
return Err(self.contextual_error(FsError::new(
FsErrorKind::InvalidState,
FsOperation::List,
"directory stream is terminal",
)));
}
self.check_deadline(now)
}
pub(crate) fn finish_next(
&mut self,
result: FsResult<Option<DirEntry>>,
now: Instant,
) -> FsResult<Option<DirEntry>> {
let entry = match result {
Ok(entry) => entry,
Err(error) => {
self.state = DirectoryStreamState::Failed;
return Err(self.contextual_error(error));
}
};
self.check_deadline(now)?;
match entry {
Some(entry) => {
if let Err(error) =
directory_entry_validation::validate_entry(&entry, &self.scope, self.path_semantics, self.limits)
{
self.state = DirectoryStreamState::Failed;
return Err(self.contextual_error(error));
}
if let Err(message) =
crate::directory::internal::select(&entry, &self.scope, &self.options, self.path_semantics)
{
self.state = DirectoryStreamState::Failed;
return Err(self.contextual_error(directory_entry_validation::option_error(&self.scope, message)));
}
if self.options.max_depth().is_some_and(|maximum| {
self.scope
.path()
.and_then(|root| directory_entry_validation::entry_depth(root, &entry.path))
.is_some_and(|depth| depth > maximum)
}) {
self.state = DirectoryStreamState::Failed;
return Err(self.resource_limit_error("directory listing depth limit was exceeded"));
}
if self
.options
.max_entries()
.is_some_and(|maximum| self.returned_entries >= maximum)
{
self.state = DirectoryStreamState::Failed;
return Err(self.resource_limit_error("directory listing entry limit was exceeded"));
}
self.returned_entries = self.returned_entries.checked_add(1).ok_or_else(|| {
self.state = DirectoryStreamState::Failed;
self.resource_limit_error("directory listing entry count exceeded the API range")
})?;
Ok(Some(entry))
}
None => {
self.state = DirectoryStreamState::Exhausted;
Ok(None)
}
}
}
fn check_deadline(&mut self, now: Instant) -> FsResult<()> {
if self.deadline.is_some_and(|deadline| now >= deadline) {
self.state = DirectoryStreamState::Failed;
return Err(self.resource_limit_error("directory listing deadline was exceeded"));
}
Ok(())
}
fn contextual_error(&self, error: FsError) -> FsError {
let error = error
.with_operation(FsOperation::List)
.with_missing_provider(&self.provider);
match self.scope.path() {
Some(path) => error.with_missing_context(path, None, &self.provider),
None => error,
}
}
fn resource_limit_error(&self, message: &str) -> FsError {
self.contextual_error(FsError::new(
FsErrorKind::ResourceLimitExceeded,
FsOperation::List,
message,
))
}
}