use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::error::{DiaryxError, Result};
use crate::fs::AsyncFileSystem;
#[cfg(not(target_arch = "wasm32"))]
use crate::fs::{FileSystem, SyncToAsyncFs};
use crate::link_parser::LinkFormat;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(alias = "base_dir")]
pub default_workspace: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub daily_entry_folder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub editor: Option<String>,
#[serde(default, skip_serializing_if = "is_default_link_format")]
pub link_format: LinkFormat,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_server_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_session_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_workspace_id: Option<String>,
#[serde(default, skip_serializing_if = "GitConfig::is_default")]
pub git: GitConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitConfig {
#[serde(default)]
pub auto_commit: bool,
#[serde(default = "default_auto_commit_interval")]
pub auto_commit_interval_minutes: u32,
}
fn default_auto_commit_interval() -> u32 {
30
}
impl Default for GitConfig {
fn default() -> Self {
Self {
auto_commit: false,
auto_commit_interval_minutes: default_auto_commit_interval(),
}
}
}
impl GitConfig {
fn is_default(&self) -> bool {
*self == Self::default()
}
}
fn is_default_link_format(format: &LinkFormat) -> bool {
*format == LinkFormat::default()
}
impl Config {
pub fn daily_entry_dir(&self) -> PathBuf {
match &self.daily_entry_folder {
Some(folder) => {
let normalized = folder.trim_start_matches('/');
self.default_workspace.join(normalized)
}
None => self.default_workspace.clone(),
}
}
pub fn base_dir(&self) -> &PathBuf {
&self.default_workspace
}
pub fn new(default_workspace: PathBuf) -> Self {
Self {
default_workspace,
daily_entry_folder: None,
editor: None,
link_format: LinkFormat::default(),
sync_server_url: None,
sync_session_token: None,
sync_email: None,
sync_workspace_id: None,
git: GitConfig::default(),
}
}
pub fn with_options(
default_workspace: PathBuf,
daily_entry_folder: Option<String>,
editor: Option<String>,
_default_template: Option<String>,
_daily_template: Option<String>,
) -> Self {
Self {
default_workspace,
daily_entry_folder,
editor,
link_format: LinkFormat::default(),
sync_server_url: None,
sync_session_token: None,
sync_email: None,
sync_workspace_id: None,
git: GitConfig::default(),
}
}
pub async fn load_from<FS: AsyncFileSystem>(fs: &FS, path: &std::path::Path) -> Result<Self> {
let contents = fs
.read_to_string(path)
.await
.map_err(|e| DiaryxError::FileRead {
path: path.to_path_buf(),
source: e,
})?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}
pub async fn save_to<FS: AsyncFileSystem>(
&self,
fs: &FS,
path: &std::path::Path,
) -> Result<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
fs.create_dir_all(parent).await?;
}
let contents = toml::to_string_pretty(self)?;
fs.write_file(path, &contents).await?;
Ok(())
}
pub async fn load_from_or_default<FS: AsyncFileSystem>(
fs: &FS,
path: &std::path::Path,
default_workspace: PathBuf,
) -> Self {
match Self::load_from(fs, path).await {
Ok(config) => config,
Err(_) => Self::new(default_workspace),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_from_sync<FS: FileSystem>(fs: FS, path: &std::path::Path) -> Result<Self> {
futures_lite::future::block_on(Self::load_from(&SyncToAsyncFs::new(fs), path))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_to_sync<FS: FileSystem>(&self, fs: FS, path: &std::path::Path) -> Result<()> {
futures_lite::future::block_on(self.save_to(&SyncToAsyncFs::new(fs), path))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_from_or_default_sync<FS: FileSystem>(
fs: FS,
path: &std::path::Path,
default_workspace: PathBuf,
) -> Self {
futures_lite::future::block_on(Self::load_from_or_default(
&SyncToAsyncFs::new(fs),
path,
default_workspace,
))
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for Config {
fn default() -> Self {
let default_base = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("diaryx");
Self {
default_workspace: default_base,
daily_entry_folder: None,
editor: None,
link_format: LinkFormat::default(),
sync_server_url: None,
sync_session_token: None,
sync_email: None,
sync_workspace_id: None,
git: GitConfig::default(),
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Config {
pub fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|dir| dir.join("diaryx").join("config.toml"))
}
pub fn load() -> Result<Self> {
if let Some(path) = Self::config_path()
&& path.exists()
{
let contents = std::fs::read_to_string(&path)?;
let config: Config = toml::from_str(&contents)?;
return Ok(config);
}
Ok(Config::default())
}
pub fn save(&self) -> Result<()> {
let path = Self::config_path().ok_or(DiaryxError::NoConfigDir)?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let contents = toml::to_string_pretty(self)?;
std::fs::write(&path, contents)?;
Ok(())
}
pub fn init(default_workspace: PathBuf) -> Result<Self> {
Self::init_with_options(default_workspace, None)
}
pub fn init_with_options(
default_workspace: PathBuf,
daily_entry_folder: Option<String>,
) -> Result<Self> {
let config = Config {
default_workspace,
daily_entry_folder,
editor: None,
link_format: LinkFormat::default(),
sync_server_url: None,
sync_session_token: None,
sync_email: None,
sync_workspace_id: None,
git: GitConfig::default(),
};
config.save()?;
Ok(config)
}
}
#[cfg(target_arch = "wasm32")]
impl Default for Config {
fn default() -> Self {
Self {
default_workspace: PathBuf::from("/workspace"),
daily_entry_folder: None,
editor: None,
link_format: LinkFormat::default(),
sync_server_url: None,
sync_session_token: None,
sync_email: None,
sync_workspace_id: None,
git: GitConfig::default(),
}
}
}