Simple tool for automatic file management

chore: use macro to manage filters definitions to avoid repetion

hauleth.dev ef186dd2 38a83bf6

verified
Changed files
+102 -18
src
+102 -18
src/filters.rs
···
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,
···
fs::metadata(path).await.map(fun).unwrap_or(false)
}
+
filter! {
+
"any";
+
struct Any { filters: Box<[Box<dyn Filter>]>, };
+
(self, path) -> {
+
stream::iter(&*self.filters)
+
.any(|f| f.matches(path))
+
.await
+
}
+
}
+
+
filter! {
+
"all";
+
struct All { filters: Box<[Box<dyn Filter>]>, };
+
(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<Box<[u8]>> {
+
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!()
+
}
+
}
+
}