diff --git a/src/filters.rs b/src/filters.rs index c9e5755..65e36d6 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -2,56 +2,96 @@ use std::path::Path; use async_trait::async_trait; use serde::Deserialize; + +use tokio::io::{self, AsyncReadExt}; use tokio::fs; +use futures::prelude::*; + use crate::pattern::Pattern; #[typetag::deserialize(tag = "type")] #[async_trait] -pub trait Filter: std::fmt::Debug { +pub trait Filter: std::fmt::Debug + Sync { async fn matches(&self, path: &Path) -> bool; } -#[derive(Debug, Deserialize)] -pub struct Name(Pattern); +macro_rules! filter { + { + $name:literal; + $(#[$sattr:meta])* + struct $sname:ident {$($(#[$attr:meta])* $fname:ident : $ftype:ty,)*}; + ($self_:ident, $path:ident) -> $body:block + } => { + #[derive(Debug, serde::Deserialize)] + $(#[$sattr])* + pub struct $sname { $($(#[$attr])* $fname: $ftype),* } -#[typetag::deserialize(name = "name")] -#[async_trait] -impl Filter for Name { - async fn matches(&self, path: &Path) -> bool { - self.0.matches(path) + #[typetag::deserialize(name = $name)] + #[async_trait] + impl Filter for $sname { + async fn matches(&$self_, $path: &Path) -> bool { + $body + } + } + }; + + { + $name:literal; + $(#[$sattr:meta])* + struct $sname:ident ($($field:ty),*); + ($self_:ident, $path:ident) -> $body:block + } => { + #[derive(Debug, serde::Deserialize)] + $(#[$sattr])* + pub struct $sname ($($field),*); + + #[typetag::deserialize(name = $name)] + #[async_trait] + impl Filter for $sname { + async fn matches(&$self_, $path: &Path) -> bool { + $body + } + } } } -#[derive(Debug, Deserialize)] -pub struct Size { - size: u64, - #[serde(with = "Ordering")] - ordering: std::cmp::Ordering, +filter! { + "name"; + struct Name(Pattern); + (self, path) -> { + self.0.matches(path) + } } #[derive(Deserialize)] #[serde(remote = "std::cmp::Ordering")] +#[serde(rename_all = "snake_case")] enum Ordering { Less, Equal, Greater, } -#[typetag::deserialize(name = "size")] -#[async_trait] -impl Filter for Size { - async fn matches(&self, path: &Path) -> bool { +filter! { + "size"; + struct Size { + size: u64, + #[serde(with = "Ordering")] + ordering: std::cmp::Ordering, + }; + (self, path) -> { with_metadata(path, |metadata| { let len = metadata.len(); - self.size.cmp(&len) == self.ordering + len.cmp(&self.size) == self.ordering }) .await } } #[derive(Debug, Deserialize)] +#[serde(tag = "is", rename_all = "snake_case")] pub enum FileType { Dir, File, @@ -84,8 +124,52 @@ where fs::metadata(path).await.map(fun).unwrap_or(false) } +filter! { + "any"; + struct Any { filters: Box<[Box]>, }; + (self, path) -> { + stream::iter(&*self.filters) + .any(|f| f.matches(path)) + .await + } +} + +filter! { + "all"; + struct All { filters: Box<[Box]>, }; + (self, path) -> { + stream::iter(&*self.filters) + .all(|f| f.matches(path)) + .await + } +} + #[derive(Debug, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum Magic { Mime(String), Bytes(Box<[u8]>), } + +async fn read_first_bytes(n: usize, path: &Path) -> io::Result> { + let mut file = fs::File::open(path).await?; + + let mut buf = vec![0; n]; + + file.read_exact(&mut buf).await?; + + Ok(buf.into()) +} + +#[typetag::deserialize(name = "content_type")] +#[async_trait] +impl Filter for Magic { + async fn matches(&self, file: &Path) -> bool { + match *self { + Magic::Bytes(ref bytes) => { + read_first_bytes(bytes.len(), file).await.map(|read| read == *bytes).unwrap_or(false) + }, + Magic::Mime(_) => unimplemented!() + } + } +}