timewall 1.0.0

All-in-one tool for Apple dynamic HEIF wallpapers on GNU/Linux
use std::fs::File;
use std::io::Write;
use std::{
    env, fs,
    path::{Path, PathBuf},
};

use anyhow::{bail, Context, Ok, Result};
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};

use crate::constants::{APP_NAME, APP_QUALIFIER};
use crate::geo::Coords;

const CONFIG_FILE_NAME: &str = "config.toml";

#[derive(Deserialize, Serialize, Debug)]
pub struct Setter {
    pub command: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct Config {
    pub location: Coords,
    pub setter: Option<Setter>,
}

impl Config {
    pub fn find() -> Result<Self> {
        let config_dir = if let Result::Ok(path_str) = env::var("TIMEWALL_CONFIG_DIR") {
            PathBuf::from(path_str)
        } else {
            match ProjectDirs::from(APP_QUALIFIER, "", APP_NAME) {
                Some(app_dirs) => app_dirs.config_dir().to_path_buf(),
                None => bail!("couldn't determine user's home directory"),
            }
        };
        Config::load_or_create(config_dir.join(CONFIG_FILE_NAME))
    }

    fn load_or_create<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        if path.exists() {
            Config::load(path)
        } else {
            let config_dir = path.parent().unwrap();
            if !config_dir.exists() {
                fs::create_dir_all(config_dir).context("couldn't create config directory")?;
            }
            let config = Config::default();
            config
                .write(path)
                .with_context(|| "couldn't write the configuration file")?;
            eprintln!("Default config written to {}.", path.display());
            eprintln!("You should probably adjust it to your needs!");
            Ok(config)
        }
    }

    fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        let config_str =
            fs::read_to_string(path).with_context(|| "couldn't read the configuration file")?;
        let config: Config =
            toml::from_str(&config_str).with_context(|| "couldn't parse the configuation file")?;
        Ok(config)
    }

    fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path = path.as_ref();
        let mut config_file = File::create(path)?;
        config_file.write_all(toml::to_string_pretty(self)?.as_bytes())?;
        Ok(())
    }

    pub fn setter_command(&self) -> Option<&Vec<String>> {
        self.setter.as_ref().map(|s| &s.command)
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            location: Coords {
                lat: 51.11,
                lon: 17.02,
            },
            setter: None,
        }
    }
}