use crate::entry::{DirectoryEntry, EntryType};
use crate::error::{PredicateError, PredicateFailure};
use crate::pattern::{CaseSensitivity, NamePattern};
use crate::timestamp::WindowsFileTimestamp;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ComparisonOperator {
Less,
LessOrEqual,
Equal,
NotEqual,
GreaterOrEqual,
Greater,
}
impl ComparisonOperator {
fn apply<T: Ord>(self, entry: T, value: T) -> bool {
match self {
ComparisonOperator::Less => entry < value,
ComparisonOperator::LessOrEqual => entry <= value,
ComparisonOperator::Equal => entry == value,
ComparisonOperator::NotEqual => entry != value,
ComparisonOperator::GreaterOrEqual => entry >= value,
ComparisonOperator::Greater => entry > value,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TimestampField {
Creation,
LastAccess,
LastWrite,
Change,
}
impl TimestampField {
fn read(self, entry: &DirectoryEntry) -> WindowsFileTimestamp {
match self {
TimestampField::Creation => entry.creation_time(),
TimestampField::LastAccess => entry.last_access_time(),
TimestampField::LastWrite => entry.last_write_time(),
TimestampField::Change => entry.change_time(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PredicateClause {
Name {
pattern: NamePattern,
case: CaseSensitivity,
negated: bool,
},
NameInSet {
patterns: Vec<NamePattern>,
case: CaseSensitivity,
negated: bool,
},
IsType {
entry_type: EntryType,
negated: bool,
},
IsReparsePoint {
negated: bool,
},
ReparseTag {
tag: u32,
negated: bool,
},
AttributesAllSet(u32),
AttributesAllClear(u32),
LogicalSize {
operator: ComparisonOperator,
value: u64,
},
AllocationSize {
operator: ComparisonOperator,
value: u64,
},
Timestamp {
field: TimestampField,
operator: ComparisonOperator,
value: WindowsFileTimestamp,
},
}
impl PredicateClause {
fn validate(&self) -> Result<(), PredicateError> {
match self {
PredicateClause::AttributesAllSet(0) | PredicateClause::AttributesAllClear(0) => {
Err(PredicateError::new(PredicateFailure::EmptyAttributeMask))
}
PredicateClause::NameInSet { patterns, .. } if patterns.is_empty() => {
Err(PredicateError::new(PredicateFailure::EmptyNameSet))
}
_ => Ok(()),
}
}
#[must_use]
pub fn matches(&self, entry: &DirectoryEntry) -> bool {
match self {
PredicateClause::Name {
pattern,
case,
negated,
} => pattern.matches(entry.name(), *case) != *negated,
PredicateClause::NameInSet {
patterns,
case,
negated,
} => {
let any = patterns
.iter()
.any(|pattern| pattern.matches(entry.name(), *case));
any != *negated
}
PredicateClause::IsType {
entry_type,
negated,
} => (entry.entry_type() == *entry_type) != *negated,
PredicateClause::IsReparsePoint { negated } => entry.is_reparse_point() != *negated,
PredicateClause::ReparseTag { tag, negated } => {
(entry.reparse_tag() == Some(*tag)) != *negated
}
PredicateClause::AttributesAllSet(mask) => entry.attributes() & mask == *mask,
PredicateClause::AttributesAllClear(mask) => entry.attributes() & mask == 0,
PredicateClause::LogicalSize { operator, value } => {
operator.apply(entry.logical_size(), *value)
}
PredicateClause::AllocationSize { operator, value } => {
operator.apply(entry.allocation_size(), *value)
}
PredicateClause::Timestamp {
field,
operator,
value,
} => operator.apply(field.read(entry), *value),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct QueryByExample {
clauses: Vec<PredicateClause>,
}
impl QueryByExample {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, clause: PredicateClause) -> Result<(), PredicateError> {
clause.validate()?;
self.clauses.push(clause);
Ok(())
}
pub fn with(mut self, clause: PredicateClause) -> Result<Self, PredicateError> {
self.push(clause)?;
Ok(self)
}
#[must_use]
pub fn clauses(&self) -> &[PredicateClause] {
&self.clauses
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.clauses.is_empty()
}
#[must_use]
pub fn matches(&self, entry: &DirectoryEntry) -> bool {
self.clauses.iter().all(|clause| clause.matches(entry))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EntryPredicate {
QueryByExample(QueryByExample),
}
impl EntryPredicate {
#[must_use]
pub fn matches(&self, entry: &DirectoryEntry) -> bool {
match self {
EntryPredicate::QueryByExample(query) => query.matches(entry),
}
}
#[must_use]
pub fn matches_everything(&self) -> bool {
match self {
EntryPredicate::QueryByExample(query) => query.is_empty(),
}
}
}
impl Default for EntryPredicate {
fn default() -> Self {
EntryPredicate::QueryByExample(QueryByExample::new())
}
}
impl From<QueryByExample> for EntryPredicate {
fn from(query: QueryByExample) -> Self {
EntryPredicate::QueryByExample(query)
}
}
#[cfg(test)]
mod tests;