use std::fmt::Debug;
use std::path::{Path};
use std::time::Duration;
use once_cell::sync::{OnceCell};
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio::sync::{RwLock, RwLockReadGuard};
pub struct Artifact<T> {
data: OnceCell<RwLock<T>>,
}
impl<T: Debug + serde::de::DeserializeOwned + Send + Sync + 'static> Artifact<T> {
pub const fn new() -> Self {
Artifact {
data: OnceCell::new(),
}
}
pub async fn init(&self, artifact_file: &str) -> Result<(), ArtifactError> {
if self.data.get().is_none() {
let artifacts_path = get_env_or_default("ARTIFACTS_PATH", "artifacts".to_string());
let path = Path::new(&artifacts_path).join(artifact_file);
let data = get_data::<T>(&path).await.map_err(|err| ArtifactError::InitializationError(err.to_string()))?;
self.data.set(RwLock::new(data)).unwrap();
}
Ok(())
}
pub async fn get(&self) -> Result<RwLockReadGuard<'_, T>, ArtifactError> {
let data_lock = self.data.get().expect("Artifact is not initialized");
Ok(data_lock.read().await)
}
pub async fn update(&self, new_data: T) -> Result<(), ArtifactError> {
let data_lock = self.data.get().expect("Artifact is not initialized");
let mut data = data_lock.write().await;
*data = new_data;
Ok(())
}
pub async fn watch(&self, artifact_file: String, interval_secs: u64) -> Result<(), ArtifactError> {
let artifacts_path = get_env_or_default("ARTIFACTS_PATH", "artifacts".to_string());
let path = Path::new(&artifacts_path).join(artifact_file);
loop {
tokio::time::sleep(Duration::from_secs(interval_secs)).await;
match get_data::<T>(&path).await {
Ok(new_data) => {
if let Err(e) = self.update(new_data).await {
return Err(ArtifactError::UpdateError(e.to_string()));
}
}
Err(e) => {
return Err(ArtifactError::WatchError(e.to_string()));
}
}
}
}
}
async fn get_data<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T, ArtifactError> {
let mut file = File::open(path).await.map_err(|err| ArtifactError::IoError(err))?;
let mut contents = String::new();
file.read_to_string(&mut contents).await.map_err(|err| ArtifactError::IoError(err))?;
let data: T = serde_json::from_str(&contents).map_err(|err| ArtifactError::SerdeError(err))?;
Ok(data)
}
pub fn get_env_or_default(key: &str, default: String) -> String {
std::env::var(key).unwrap_or(default)
}
#[derive(Debug)]
pub enum ArtifactError {
IoError(std::io::Error),
SerdeError(serde_json::Error),
InitializationError(String),
UpdateError(String),
WatchError(String),
}
impl std::fmt::Display for ArtifactError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArtifactError::IoError(err) => write!(f, "I/O error: {}", err),
ArtifactError::SerdeError(err) => write!(f, "JSON serialization/deserialization error: {}", err),
ArtifactError::InitializationError(msg) => write!(f, "Initialization error: {}", msg),
ArtifactError::UpdateError(msg) => write!(f, "Update error: {}", msg),
ArtifactError::WatchError(msg) => write!(f, "Watch error: {}", msg),
}
}
}
impl std::error::Error for ArtifactError {}
impl From<std::io::Error> for ArtifactError {
fn from(err: std::io::Error) -> Self {
ArtifactError::IoError(err)
}
}
impl From<serde_json::Error> for ArtifactError {
fn from(err: serde_json::Error) -> Self {
ArtifactError::SerdeError(err)
}
}