0
0
Fork 0

add persistent state and track active list

This commit is contained in:
Adrian Wannenmacher 2023-09-30 03:57:23 +02:00
parent d57ed908e2
commit 457dcdb326
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
5 changed files with 105 additions and 11 deletions

View File

@ -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 {

View File

@ -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)]

View File

@ -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)> {

63
src/state.rs Normal file
View File

@ -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> {
Self::load_file(&directory.join("state.json"))
}
pub fn load_file(file: &Path) -> anyhow::Result<Self> {
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,
}
}
}

View File

@ -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")?;