Simple tool for automatic file management
1use std::path::Path; 2 3use async_trait::async_trait; 4use serde::Deserialize; 5 6use tokio::io::{self, AsyncReadExt}; 7use tokio::fs; 8 9use futures::prelude::*; 10 11use crate::pattern::Pattern; 12 13#[typetag::deserialize(tag = "type")] 14#[async_trait] 15pub trait Filter: std::fmt::Debug + Sync { 16 async fn matches(&self, path: &Path) -> bool; 17} 18 19macro_rules! filter { 20 { 21 $name:literal; 22 $(#[$sattr:meta])* 23 struct $sname:ident {$($(#[$attr:meta])* $fname:ident : $ftype:ty,)*}; 24 ($self_:ident, $path:ident) -> $body:block 25 } => { 26 #[derive(Debug, serde::Deserialize)] 27 $(#[$sattr])* 28 pub struct $sname { $($(#[$attr])* $fname: $ftype),* } 29 30 #[typetag::deserialize(name = $name)] 31 #[async_trait] 32 impl Filter for $sname { 33 async fn matches(&$self_, $path: &Path) -> bool { 34 $body 35 } 36 } 37 }; 38 39 { 40 $name:literal; 41 $(#[$sattr:meta])* 42 struct $sname:ident ($($field:ty),*); 43 ($self_:ident, $path:ident) -> $body:block 44 } => { 45 #[derive(Debug, serde::Deserialize)] 46 $(#[$sattr])* 47 pub struct $sname ($($field),*); 48 49 #[typetag::deserialize(name = $name)] 50 #[async_trait] 51 impl Filter for $sname { 52 async fn matches(&$self_, $path: &Path) -> bool { 53 $body 54 } 55 } 56 } 57} 58 59filter! { 60 "name"; 61 struct Name(Pattern); 62 (self, path) -> { 63 self.0.matches(path) 64 } 65} 66 67#[derive(Deserialize)] 68#[serde(remote = "std::cmp::Ordering")] 69#[serde(rename_all = "snake_case")] 70enum Ordering { 71 Less, 72 Equal, 73 Greater, 74} 75 76filter! { 77 "size"; 78 struct Size { 79 size: u64, 80 #[serde(with = "Ordering")] 81 ordering: std::cmp::Ordering, 82 }; 83 (self, path) -> { 84 with_metadata(path, |metadata| { 85 let len = metadata.len(); 86 87 len.cmp(&self.size) == self.ordering 88 }) 89 .await 90 } 91} 92 93#[derive(Debug, Deserialize)] 94#[serde(tag = "is", rename_all = "snake_case")] 95pub enum FileType { 96 Dir, 97 File, 98 Symlink, 99} 100 101#[typetag::deserialize(name = "file_type")] 102#[async_trait] 103impl Filter for FileType { 104 async fn matches(&self, path: &Path) -> bool { 105 use FileType::*; 106 107 with_metadata(path, |metadata| { 108 let ft = metadata.file_type(); 109 110 match *self { 111 Dir => ft.is_dir(), 112 File => ft.is_file(), 113 Symlink => ft.is_symlink(), 114 } 115 }) 116 .await 117 } 118} 119 120async fn with_metadata<F>(path: &Path, fun: F) -> bool 121where 122 F: FnOnce(std::fs::Metadata) -> bool, 123{ 124 fs::metadata(path).await.map(fun).unwrap_or(false) 125} 126 127filter! { 128 "any"; 129 struct Any { filters: Box<[Box<dyn Filter>]>, }; 130 (self, path) -> { 131 stream::iter(&*self.filters) 132 .any(|f| f.matches(path)) 133 .await 134 } 135} 136 137filter! { 138 "all"; 139 struct All { filters: Box<[Box<dyn Filter>]>, }; 140 (self, path) -> { 141 stream::iter(&*self.filters) 142 .all(|f| f.matches(path)) 143 .await 144 } 145} 146 147#[derive(Debug, Deserialize)] 148#[serde(rename_all = "snake_case")] 149pub enum Magic { 150 Mime(String), 151 Bytes(Box<[u8]>), 152} 153 154async fn read_first_bytes(n: usize, path: &Path) -> io::Result<Box<[u8]>> { 155 let mut file = fs::File::open(path).await?; 156 157 let mut buf = vec![0; n]; 158 159 file.read_exact(&mut buf).await?; 160 161 Ok(buf.into()) 162} 163 164#[typetag::deserialize(name = "content_type")] 165#[async_trait] 166impl Filter for Magic { 167 async fn matches(&self, file: &Path) -> bool { 168 match *self { 169 Magic::Bytes(ref bytes) => { 170 read_first_bytes(bytes.len(), file).await.map(|read| read == *bytes).unwrap_or(false) 171 }, 172 Magic::Mime(_) => unimplemented!() 173 } 174 } 175}