use serde::{Serialize, de::DeserializeOwned};
use std::fs;
use std::io;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use crate::ConfigError;
use crate::Result;
use crate::core::constant::DEFAULT_FILE_NAME;
#[cfg(all(windows, feature = "uwp"))]
use crate::core::dir::uwp_local_folder_dir;
use crate::core::dir::{default_config_dir, default_runtime_app_name};
use crate::core::json::{JsonFormat, deserialize, serialize};
use crate::core::save::save_to_path;
use crate::core::validation::{validate_path_component, validate_plain_file_name};
pub mod constant;
mod dir;
pub mod error;
mod json;
mod save;
pub mod validation;
pub use save::SaveMode;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone)]
pub struct ConfigManager<T> {
folder_path: PathBuf,
file_name: String,
json_format: JsonFormat,
save_mode: SaveMode,
_marker: PhantomData<T>,
}
impl<T> ConfigManager<T>
where
T: Serialize + DeserializeOwned,
{
pub fn new() -> Self {
Self::from_parts(
default_config_dir().join(default_runtime_app_name()),
DEFAULT_FILE_NAME,
)
}
pub fn for_app(app_name: &str) -> Result<Self> {
let app_name = validate_path_component(app_name)?;
Ok(Self::from_parts(
default_config_dir().join(app_name),
DEFAULT_FILE_NAME,
))
}
fn from_parts<P>(folder_path: P, file_name: &str) -> Self
where
P: Into<PathBuf>,
{
Self {
folder_path: folder_path.into(),
file_name: file_name.to_string(),
json_format: JsonFormat::Pretty,
save_mode: SaveMode::Atomic,
_marker: PhantomData,
}
}
pub fn at_current_dir(mut self) -> Self {
self.folder_path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
self
}
pub fn with_root_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.folder_path = path.into();
self
}
pub fn at_custom_dir<P: Into<PathBuf>>(self, path: P) -> Self {
self.with_root_dir(path)
}
#[cfg(all(windows, feature = "uwp"))]
pub fn at_uwp_local_folder(mut self) -> Result<Self> {
self.folder_path = uwp_local_folder_dir()?;
Ok(self)
}
pub fn with_filename(mut self, name: &str) -> Self {
self.file_name = name.to_string();
self
}
pub fn try_with_filename(mut self, name: &str) -> Result<Self> {
self.file_name = validate_plain_file_name(name)?.to_string();
Ok(self)
}
pub fn disable_pretty_json(mut self) -> Self {
self.json_format = JsonFormat::Compact;
self
}
pub fn with_save_mode(mut self, mode: SaveMode) -> Self {
self.save_mode = mode;
self
}
pub fn with_direct_save(self) -> Self {
self.with_save_mode(SaveMode::Direct)
}
pub fn save_mode(&self) -> SaveMode {
self.save_mode
}
pub fn folder_path(&self) -> &Path {
&self.folder_path
}
pub fn file_name(&self) -> &str {
&self.file_name
}
pub fn path(&self) -> PathBuf {
self.folder_path.join(&self.file_name)
}
pub fn save(&self, config: &T) -> Result<()> {
let content = serialize(config, self.json_format)?;
save_to_path(&self.path(), &content, self.save_mode)
}
pub fn load(&self) -> Result<T> {
let content = fs::read_to_string(self.path())?;
deserialize(&content)
}
}
impl<T> Default for ConfigManager<T>
where
T: Serialize + DeserializeOwned,
{
fn default() -> Self {
Self::new()
}
}
impl<T> ConfigManager<T>
where
T: Serialize + DeserializeOwned + Default,
{
pub fn load_or_default(&self) -> Result<T> {
let path = self.path();
match fs::read_to_string(&path) {
Ok(content) => deserialize(&content),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
let default_config = T::default();
self.save(&default_config)?;
Ok(default_config)
}
Err(e) => Err(ConfigError::Io(e)),
}
}
pub fn update<F>(&self, f: F) -> Result<T>
where
F: FnOnce(&mut T),
{
let mut cfg = self.load_or_default()?;
f(&mut cfg);
self.save(&cfg)?;
Ok(cfg)
}
}