pub trait WatchFile{
// Provided methods
fn post_serialize(&mut self) { ... }
fn serialize<P>(
file_type: &FileType<P>,
buf: &str,
) -> Result<Self, Box<dyn Error>>
where P: AsRef<Path> + Debug + Send + Sync + 'static { ... }
fn load_if_changed<P>(
file_type: &FileType<P>,
last_modified: &mut SystemTime,
buf: &mut String,
) -> impl Future<Output = Result<bool, Error>> + Send + Sync
where P: AsRef<Path> + Debug + Send + Sync + 'static { ... }
fn reload<P>(
file_type: FileType<P>,
duration: Duration,
sender: Sender<Self>,
) -> impl Future<Output = ()> + Send + Sync + 'static
where P: AsRef<Path> + Debug + Send + Sync + 'static { ... }
fn watch_file<P>(path: P, duration: Duration) -> Receiver<Self>
where P: AsRef<Path> + Debug + Send + Sync + 'static { ... }
fn watch_file_channel<P>(
path: P,
duration: Duration,
) -> (Sender<Self>, Receiver<Self>)
where P: AsRef<Path> + Debug + Send + Sync + 'static { ... }
fn save<P>(
&self,
path: P,
) -> impl Future<Output = Result<(), ()>> + Send + Sync + '_
where P: AsRef<Path> + Debug + Send + Sync + 'static,
Self: Serialize { ... }
}Expand description
Trait for watching a file and updating a struct.
Provided Methods§
Sourcefn post_serialize(&mut self)
fn post_serialize(&mut self)
Hook to run after deserialization, useful for post-processing.
Note: This method is called after deserialization, so it is not called on initialization.
This method is called whenever the file is reloaded and re-serialized, so it’s best to avoid any expensive operations or side effects here. However, for files that don’t change often, the extra processing is negligible, since this will only run when a change is detected.
If using this method along with Self::save, note that these changes will be written to the file.
§Example
use watchfile::WatchFile;
use std::collections::HashMap;
use std::time::Duration;
#[derive(serde::Deserialize, Default, PartialEq, Debug)]
struct Config {
data: HashMap<String, String>,
}
impl WatchFile for Config {
fn post_serialize(&mut self) {
self.data.insert("key".to_string(), "value".to_string());
}
}
#[tokio::main]
async fn main() {
let config = Config::watch_file("config.json", Duration::from_millis(100));
assert_eq!(config.borrow().data.len(), 0, "Post processing doesn't run on initialization");
tokio::fs::write("config.json", "{\"data\": {}}").await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(config.borrow().data.len(), 1, "Post processing runs on reload");
tokio::fs::remove_file("config.json").await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(config.borrow().data.len(), 1, "No update on error (file not found)");
}Sourcefn serialize<P>(
file_type: &FileType<P>,
buf: &str,
) -> Result<Self, Box<dyn Error>>
fn serialize<P>( file_type: &FileType<P>, buf: &str, ) -> Result<Self, Box<dyn Error>>
Deserializes the buffer based on the file type and enabled features.
Sourcefn load_if_changed<P>(
file_type: &FileType<P>,
last_modified: &mut SystemTime,
buf: &mut String,
) -> impl Future<Output = Result<bool, Error>> + Send + Sync
fn load_if_changed<P>( file_type: &FileType<P>, last_modified: &mut SystemTime, buf: &mut String, ) -> impl Future<Output = Result<bool, Error>> + Send + Sync
Updates the buffer if the file modified time is newer than the last check.
Sourcefn reload<P>(
file_type: FileType<P>,
duration: Duration,
sender: Sender<Self>,
) -> impl Future<Output = ()> + Send + Sync + 'static
fn reload<P>( file_type: FileType<P>, duration: Duration, sender: Sender<Self>, ) -> impl Future<Output = ()> + Send + Sync + 'static
Continuously checks the file for changes and sends updates on a tokio::sync::watch channel
Only updates when all of the conditions are true:
- The file has changed since the last check (intervals are determined by the duration).
- The file is successfully read and serialized.
- The serialized data is different (
!=) from the previous data.
Sourcefn watch_file<P>(path: P, duration: Duration) -> Receiver<Self>
fn watch_file<P>(path: P, duration: Duration) -> Receiver<Self>
Spawn a task that watches a file for changes and sends updates on a channel.
Same as Self::watch_file_channel but drops the “extra” sender and returns only the receiver.
The sender still exists in the background task, but it is not returned to the caller, so direct updates are not possible.
§Panics
Panics if the file type is not supported by the crate with the enabled features.
§Example
use std::time::Duration;
use watchfile::WatchFile;
use std::ops::Deref;
#[derive(serde::Deserialize, Default, PartialEq, Debug)]
struct Config {
data: String,
}
impl WatchFile for Config {}
#[tokio::main]
async fn main() {
let rx = Config::watch_file("config.json", Duration::from_secs(1));
assert_eq!(rx.borrow().deref(), &Config::default());
}Sourcefn watch_file_channel<P>(
path: P,
duration: Duration,
) -> (Sender<Self>, Receiver<Self>)
fn watch_file_channel<P>( path: P, duration: Duration, ) -> (Sender<Self>, Receiver<Self>)
Spawn a task that watches a file for changes and sends updates on a channel.
§Panics
Panics if the file type is not supported by the crate with the enabled features.
§Example
use std::time::Duration;
use watchfile::WatchFile;
use std::ops::Deref;
#[derive(serde::Deserialize, Default, PartialEq, Debug)]
struct Config {
data: String,
}
impl WatchFile for Config {}
#[tokio::main]
async fn main() {
let (tx, mut rx) = Config::watch_file_channel("config.json", Duration::from_secs(1));
assert_eq!(tx.sender_count(), 2);
assert_eq!(tx.receiver_count(), 1);
assert_eq!(rx.borrow().deref(), &Config::default());
}Sourcefn save<P>(
&self,
path: P,
) -> impl Future<Output = Result<(), ()>> + Send + Sync + '_
fn save<P>( &self, path: P, ) -> impl Future<Output = Result<(), ()>> + Send + Sync + '_
Save the current state of the struct to a file, with the given path.
Serializes the struct based on the file extension, make sure to enable the corresponding feature.
Replaces the previous contents of the file if it exists, otherwise creates a new file.
Can be used safely along with Self::watch_file and Self::watch_file_channel,
as those tasks do not update malformed files (e.g. in the middle of a write operation).
§Errors
Returns an error in one of the following:
- The file type is not supported by the crate with the enabled features.
- The serialization process fails (e.g. due to a missing field, mismatched types, etc.).
- The write operation fails (e.g. due to a permission error, disk full, etc.).
§Example
use watchfile::WatchFile;
use std::collections::HashMap;
use std::time::Duration;
#[derive(serde::Deserialize, serde::Serialize, Default, PartialEq, Debug, Clone)]
struct Config {
data: HashMap<String, String>,
}
impl WatchFile for Config {}
#[tokio::main]
async fn main() {
let config = Config::watch_file("config.json", Duration::from_millis(100));
let mut updated_config = config.borrow().clone();
updated_config.data.insert("key".to_string(), "value".to_string());
updated_config.save("config.json").await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(config.borrow().data.len(), 1);
assert_eq!(config.borrow().data.get("key"), Some(&"value".to_string()));
tokio::fs::remove_file("config.json").await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
assert!(tokio::fs::metadata("config.json").await.is_err(), "File deleted");
assert_eq!(config.borrow().data.len(), 1, "No update on error (file not found)");
}Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.