Simple tool for automatic file management
1use color_eyre::eyre::Result; 2 3use std::path::{Path, PathBuf}; 4 5use tokio::fs; 6use tokio::process::Command; 7use tokio_stream::wrappers::ReadDirStream; 8 9use futures::prelude::*; 10 11use serde::Deserialize; 12 13use crate::filters::Filter; 14 15/// Definition of the job files 16#[derive(Debug, Deserialize)] 17pub struct Job { 18 filters: Vec<Box<dyn Filter>>, 19 location: PathBuf, 20 actions: Vec<Action>, 21} 22 23impl Job { 24 pub async fn run(&self) -> Result<impl tokio_stream::Stream<Item = (Action, PathBuf)> + '_> { 25 let loc = normalise_path(&self.location); 26 27 let dir = ReadDirStream::new(fs::read_dir(&loc).await?); 28 29 Ok(async_stream::stream! { 30 for await entry in dir { 31 let entry = entry.unwrap(); 32 if self.matches_filters(&entry.path()).await { 33 for action in &self.actions { 34 yield (action.clone(), entry.path()) 35 } 36 } 37 } 38 }) 39 } 40 41 async fn matches_filters(&self, path: &Path) -> bool { 42 stream::iter(&self.filters).all(|f| f.matches(path)).await 43 } 44} 45 46/// Actions available for file 47#[derive(Clone, Debug, Deserialize)] 48#[serde(untagged)] 49pub enum Action { 50 /// Run given script with 1st argument. It will be ran in parent directory for given file 51 Script { script: Box<Path> }, 52 /// Move given file to new destination 53 Move { move_to: Box<Path> }, 54 /// Print message and do nothing 55 Echo { message: String }, 56 /// Move to trash 57 Trash, 58} 59 60impl Action { 61 pub async fn execute(self, source: PathBuf) { 62 match self { 63 Action::Script { ref script } => { 64 Command::new(script.as_ref()) 65 .arg(&source) 66 .current_dir(source.parent().unwrap()) 67 .spawn() 68 .expect("Couldnt spawn process") 69 .wait() 70 .await 71 .expect("Child exited abnormally"); 72 } 73 74 Action::Move { 75 move_to: ref dest_dir, 76 } => { 77 let dest = normalise_path(dest_dir).join(source.file_name().unwrap()); 78 if let Err(err) = fs::rename(&source, &dest).await { 79 if err.raw_os_error() == Some(libc::EXDEV) { 80 panic!("X dev"); 81 } else { 82 panic!("Cannot move {source:?} -> {dest:?}: {err:?}"); 83 } 84 } 85 } 86 87 Action::Echo { ref message } => println!("{source:?} - {message}"), 88 89 Action::Trash => trash::delete(source).unwrap(), 90 } 91 } 92} 93 94fn normalise_path(path: &Path) -> PathBuf { 95 match path.strip_prefix("~") { 96 Ok(prefix) => std::env::home_dir().unwrap().join(prefix), 97 Err(_) => path.to_owned(), 98 } 99}