diff --git a/src/cli/list.rs b/src/cli/list.rs index f6cf516..c43ba0a 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -2,6 +2,12 @@ use clap::Subcommand; #[derive(Debug, Subcommand)] pub enum ListCommand { + /// Activate a download list. + #[clap(visible_alias = "a")] + Activate { + /// The name of the list to activate. + name: String, + }, /// Create a new download list. #[clap(visible_alias = "c", visible_alias = "new")] Create { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 64dd911..1d2004e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -9,6 +9,10 @@ pub use list::*; pub struct CLI { #[clap(subcommand)] pub command: Command, + /// Print the state before and after executing the specified operation. + #[cfg(debug_assertions)] + #[clap(long)] + pub debug_state: bool, } #[derive(Debug, Subcommand)] diff --git a/src/main.rs b/src/main.rs index 59b3176..3a74e80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,38 +7,48 @@ use anyhow::{ensure, Context}; use clap::Parser; use directories::ProjectDirs; -use crate::store::DownloadListStore; +use crate::{state::State, store::DownloadListStore}; mod cli; mod data; +mod state; mod store; fn main() -> anyhow::Result<()> { human_panic::setup_panic!(); let cli = cli::CLI::parse(); - let (state, data) = prepare_directories()?; + + let (state_dir, data) = prepare_directories()?; + let mut state = State::load(&state_dir).context("failed to read state")?; + + #[cfg(debug_assertions)] + if cli.debug_state { + dbg!(&state); + } match cli.command { cli::Command::List { command } => match command { + cli::ListCommand::Activate { name } => { + let _ = DownloadListStore::load(&name, &data)?; + state.set_active_list(name); + } cli::ListCommand::Create { name, description } => { let mut list = DownloadListStore::new(name, &data)?; list.set_description(description); - list.save() + list.save()?; } - cli::ListCommand::Delete { name } => DownloadListStore::delete(&name, &data), + cli::ListCommand::Delete { name } => DownloadListStore::delete(&name, &data)?, cli::ListCommand::Info { name } => { let list = DownloadListStore::load(&name, &data)?; println!("name: {}", list.name()); println!("description: {}", list.description()); - - Ok(()) } - cli::ListCommand::List => Ok(DownloadListStore::list(&data)? + cli::ListCommand::List => DownloadListStore::list(&data)? .into_iter() - .for_each(|n| println!("{n}"))), + .for_each(|n| println!("{n}")), cli::ListCommand::Update { name, description } => { let mut list = DownloadListStore::load(&name, &data)?; @@ -46,10 +56,21 @@ fn main() -> anyhow::Result<()> { list.set_description(description); } - list.save() + list.save()?; } }, } + + #[cfg(debug_assertions)] + if cli.debug_state { + dbg!(&state); + } + + if state.changed() { + state.save(&state_dir)?; + } + + Ok(()) } fn prepare_directories() -> anyhow::Result<(PathBuf, PathBuf)> { diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..49428ec --- /dev/null +++ b/src/state.rs @@ -0,0 +1,63 @@ +use std::{fs, path::Path}; + +use anyhow::Context; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct State { + active_list: String, + #[serde(skip, default)] + changed: bool, +} + +impl State { + pub fn active_list(&self) -> &str { + &self.active_list + } + + pub fn set_active_list(&mut self, active_list: String) { + self.active_list = active_list; + self.changed = true; + } + + pub fn changed(&self) -> bool { + self.changed + } +} + +impl State { + pub fn load(directory: &Path) -> anyhow::Result { + Self::load_file(&directory.join("state.json")) + } + + pub fn load_file(file: &Path) -> anyhow::Result { + if !file.is_file() { + return Ok(Self::default()); + } + + serde_json::from_slice(&fs::read(file).context("failed to read state file")?) + .context("failed to deserialize state") + } + + pub fn save(&mut self, directory: &Path) -> anyhow::Result<()> { + self.save_file(&directory.join("state.json")) + } + + pub fn save_file(&mut self, file: &Path) -> anyhow::Result<()> { + self.changed = false; + fs::write( + file, + serde_json::to_vec(&self).context("failed to serialize state")?, + ) + .context("failed to write state file") + } +} + +impl Default for State { + fn default() -> Self { + Self { + active_list: String::from("default"), + changed: false, + } + } +} diff --git a/src/store.rs b/src/store.rs index fd2197c..0577fe7 100644 --- a/src/store.rs +++ b/src/store.rs @@ -26,7 +26,7 @@ impl DownloadListStore { .read(true) .write(true) .open(&path) - .context("failed to create list file")?; + .context(format!("failed to create list file: {}", path.display()))?; file.lock_exclusive() .context("failed to acquire list file lock")?; @@ -45,7 +45,7 @@ impl DownloadListStore { .read(true) .write(true) .open(&path) - .context("failed to open list file")?; + .context(format!("failed to open list file: {}", path.display()))?; file.lock_exclusive() .context("failed to acquire list file lock")?;