diff --git a/src/main.rs b/src/main.rs index 219c201..8dc6427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,10 @@ use anyhow::Context; use clap::{Parser, Subcommand}; use config::Config; use directories::{ProjectDirs, UserDirs}; +use persistent_state::PersistentState; mod config; +mod persistent_state; static USER_DIRS: OnceLock = OnceLock::new(); static PROJ_DIRS: OnceLock = OnceLock::new(); @@ -35,16 +37,22 @@ fn main() -> anyhow::Result<()> { // prepare for operation let cfg = Config::read_from_default_file().context("failed to load config")?; let downloader = cfg.downloader().context("failed to create downloader")?; + let mut persistent = + PersistentState::read_from_default_file().context("failed to load persistent state")?; match cli.command { CMD::Config => { println!("{cfg}"); - Ok(()) } CMD::License => { panic!("license passed first check"); } + CMD::PersistentState => { + println!("{persistent}"); + } } + + persistent.save_to_default_file() } #[derive(Parser)] @@ -56,8 +64,10 @@ struct CLI { #[derive(Subcommand)] enum CMD { - /// Print out the current configuration. + /// Print the current configuration. Config, /// Print the EUPL 1.2, under which this program is licensed. License, + /// Print the current persistent state. + PersistentState, } diff --git a/src/persistent_state.rs b/src/persistent_state.rs new file mode 100644 index 0000000..44f009c --- /dev/null +++ b/src/persistent_state.rs @@ -0,0 +1,84 @@ +use std::{ + fmt::Display, + fs::{create_dir_all, File}, + io::{BufReader, BufWriter, Read, Write}, + path::Path, +}; + +use anyhow::{bail, ensure, Context}; +use serde::{Deserialize, Serialize}; + +use crate::PROJ_DIRS; + +const PERSISTENT_FILE: &str = "persistent_state.json"; + +#[derive(Debug, Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct PersistentState {} + +impl PersistentState { + pub fn read_from_default_file() -> anyhow::Result { + let dirs = PROJ_DIRS.get().expect("directories not initialized"); + + let mut path = dirs.preference_dir().to_path_buf(); + path.push(PERSISTENT_FILE); + + if path.is_file() { + Self::read_from_file(&path) + } else if !path.exists() { + Ok(Self::default()) + } else { + bail!("persistent state file is neither file nor nonexistant") + } + } + + pub fn read_from_file(path: &Path) -> anyhow::Result { + File::open(path) + .context("failed to open persistent state file") + .map(|r| BufReader::new(r)) + .and_then(|r| Self::read_from(r)) + } + + pub fn read_from(reader: impl Read) -> anyhow::Result { + serde_json::from_reader(reader).context("failed to parse persistent state file") + } + + pub fn save_to_default_file(&self) -> anyhow::Result<()> { + let dirs = PROJ_DIRS.get().expect("directories not initialized"); + + let mut path = dirs.preference_dir().to_path_buf(); + ensure!( + path.is_dir() || !path.exists(), + "preference directory is neither directory nor nonexistent" + ); + + if !path.exists() { + create_dir_all(&path).context("failed to create program preference directory")?; + } + + path.push(PERSISTENT_FILE); + ensure!( + path.is_file() || !path.exists(), + "persistent state file is neither file nor nonexistant" + ); + + self.save_to_file(&path) + } + + pub fn save_to_file(&self, path: &Path) -> anyhow::Result<()> { + File::create(path) + .context("failed to create persistent state file") + .map(|w| BufWriter::new(w)) + .and_then(|w| self.save_to(w)) + } + + pub fn save_to(&self, writer: impl Write) -> anyhow::Result<()> { + serde_json::to_writer(writer, &self).context("failed to write persistent state file") + } +} + +impl Display for PersistentState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "no persistent state yet") + } +}