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}