file_watcher/
lib.rs

1#![deny(clippy::pedantic)]
2// #![warn(missing_docs)]
3
4use set_error::ChangeError;
5
6use std::{
7    path::Path,
8    rc::Rc,
9    thread,
10    time::{Duration, SystemTime},
11};
12
13#[derive(Clone)]
14pub struct FileListBuilder<T: Clone> {
15    files: Vec<WatchedFile<T>>,
16    interval: Duration,
17    max_retries: Option<u32>,
18    open_file_func: Rc<Fn(&str) -> WatchingFuncResult<T>>,
19    run_only_once: bool,
20}
21
22#[derive(Clone)]
23pub struct WatchedFile<T> {
24    path: String,
25    date_modified: SystemTime,
26    functions_on_run: Vec<Rc<Fn(T) -> WatchingFuncResult<T>>>,
27    function_on_end: Rc<Fn(T) -> Result<(), String>>,
28}
29
30pub enum WatchingFuncResult<T> {
31    Success(T),
32    Retry(String),
33    Fail(String),
34}
35use WatchingFuncResult::{Fail, Retry, Success};
36
37impl<T: Clone> FileListBuilder<T> {
38    pub fn new<F: 'static + Fn(&str) -> WatchingFuncResult<T>>(open_func: F) -> Self {
39        Self {
40            files: Vec::new(),
41            interval: Duration::from_millis(1000),
42            max_retries: None,
43            open_file_func: Rc::new(open_func),
44            run_only_once: false,
45        }
46    }
47    pub fn run_only_once(mut self, q: bool) -> Self {
48        self.run_only_once = q;
49        self
50    }
51    pub fn add_file(&mut self, file: WatchedFile<T>) {
52        self.files.push(file);
53    }
54    pub fn with_interval(mut self, inter: Duration) -> Self {
55        self.interval = inter;
56        self
57    }
58    pub fn with_max_retries(mut self, re: u32) -> Self {
59        self.max_retries = Some(re);
60        self
61    }
62    pub fn launch(mut self) -> Result<(), String> {
63        let mut on_first_run = self.files.len() + 1;
64        loop {
65            for mut file in &mut self.files {
66                if on_first_run != 0 {
67                    on_first_run -= 1
68                }
69                if (on_first_run != 0) || (file.date_modified != date_modified(&file.path)?) {
70                    file.date_modified = date_modified(&file.path)?;
71                    let open_file_func_as_not_mut = self.open_file_func.clone();
72                    let mut file_data = keep_doing_until(self.max_retries, self.interval, || {
73                        (open_file_func_as_not_mut)(&file.path)
74                    })?;
75                    for function_to_run in file.functions_on_run.clone() {
76                        file_data = keep_doing_until(self.max_retries, self.interval, || {
77                            function_to_run(file_data.clone())
78                        })?
79                    }
80                    let mut retries = self.max_retries;
81                    loop {
82                        match (file.function_on_end)(file_data.clone()) {
83                            Ok(_) => break,
84                            Err(s) => {
85                                retries = retries.map(|x| x - 1);
86                                match retries {
87                                    Some(n) if n == 0 => {
88                                        return Err(String::from("no more retries"))
89                                    }
90                                    _ => {
91                                        println!("{}", s);
92                                        thread::sleep(self.interval);
93                                        continue;
94                                    }
95                                }
96                            }
97                        }
98                    }
99                    thread::sleep(self.interval);
100                }
101            }
102            if self.run_only_once {
103                return Ok(());
104            }
105        }
106    }
107}
108
109fn keep_doing_until<F, T>(mut retries: Option<u32>, interval: Duration, f: F) -> Result<T, String>
110where
111    F: Fn() -> WatchingFuncResult<T>,
112{
113    Ok(loop {
114        match f() {
115            Success(t) => break t,
116            Fail(s) => return Err(s),
117            Retry(s) => {
118                retries = retries.map(|x| x - 1);
119                match retries {
120                    Some(n) if n == 0 => return Err(String::from("no more retries")),
121                    _ => {
122                        println!("{}", s);
123                        thread::sleep(interval);
124                        continue;
125                    }
126                }
127            }
128        }
129    })
130}
131
132impl<T> WatchedFile<T> {
133    pub fn new<G: 'static + Fn(T) -> Result<(), String>>(
134        path: &str,
135        end_func: G,
136    ) -> Result<Self, String> {
137        Ok(Self {
138            path: path.to_string(),
139            date_modified: date_modified(&path)?,
140            functions_on_run: Vec::new(),
141            function_on_end: Rc::new(end_func),
142        })
143    }
144    pub fn add_func<F: 'static + Fn(T) -> WatchingFuncResult<T>>(&mut self, func: F) {
145        self.functions_on_run.push(Rc::new(func));
146    }
147}
148
149fn date_modified(path: &str) -> Result<SystemTime, String> {
150    Ok(Path::new(path)
151        .metadata()
152        .set_error(&format!("failed to open file {} metadata", path))?
153        .modified()
154        .set_error(&format!("failed to find files date modified {}", path))?)
155}