use anyhow::{Context, Result, bail};
use reqwest::Client;
use tokio::fs;
use tokio::sync::OnceCell;
use crate::config::LoadedConfig;
use crate::config::path::get_config_path;
use crate::log_info;
#[derive(Debug, Clone)]
pub struct RemoteConfigManager {
url: String,
config: OnceCell<String>,
}
impl RemoteConfigManager {
#[must_use]
pub const fn new(url: String) -> Self {
Self {
url,
config: OnceCell::const_new(),
}
}
pub async fn fetch(&self) -> Result<()> {
self.config
.get_or_try_init(|| async {
log_info!("Fetching remote config from {}", self.url);
let client = Client::builder()
.user_agent("cutler-remote-config")
.build()?;
let resp =
client.get(&self.url).send().await.with_context(|| {
format!("Failed to fetch remote config from {}", self.url)
})?;
if !resp.status().is_success() {
bail!("Failed to fetch remote config: HTTP {}", resp.status());
}
let text = resp.text().await?;
toml::from_str::<LoadedConfig>(&text)
.with_context(|| format!("Invalid TOML config fetched from {}", self.url))?;
Ok(text)
})
.await?;
Ok(())
}
pub async fn save(&self) -> Result<()> {
let config = self.get()?;
let config_path = get_config_path();
fs::create_dir_all(
config_path
.parent()
.with_context(|| "Failed to initialize config path for remote sync.".to_string())?,
)
.await?;
fs::write(config_path, config).await?;
log_info!("Successfully saved remote config to destination.");
Ok(())
}
pub fn get(&self) -> Result<&String> {
let config = self
.config
.get()
.ok_or_else(|| anyhow::anyhow!("Remote config not fetched yet"))?;
Ok(config)
}
pub fn get_parsed(&self) -> Result<LoadedConfig> {
let config_str = self.get()?;
let config = toml::from_str::<LoadedConfig>(config_str)?;
Ok(config)
}
}