mirror of
https://github.com/TeFiLeDo/listload.git
synced 2024-11-23 12:46:17 +01:00
add persistent state and track active list
This commit is contained in:
parent
d57ed908e2
commit
457dcdb326
@ -2,6 +2,12 @@ use clap::Subcommand;
|
|||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum ListCommand {
|
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.
|
/// Create a new download list.
|
||||||
#[clap(visible_alias = "c", visible_alias = "new")]
|
#[clap(visible_alias = "c", visible_alias = "new")]
|
||||||
Create {
|
Create {
|
||||||
|
@ -9,6 +9,10 @@ pub use list::*;
|
|||||||
pub struct CLI {
|
pub struct CLI {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub command: Command,
|
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)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
39
src/main.rs
39
src/main.rs
@ -7,38 +7,48 @@ use anyhow::{ensure, Context};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
|
||||||
use crate::store::DownloadListStore;
|
use crate::{state::State, store::DownloadListStore};
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod data;
|
mod data;
|
||||||
|
mod state;
|
||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
human_panic::setup_panic!();
|
human_panic::setup_panic!();
|
||||||
let cli = cli::CLI::parse();
|
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 {
|
match cli.command {
|
||||||
cli::Command::List { command } => match 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 } => {
|
cli::ListCommand::Create { name, description } => {
|
||||||
let mut list = DownloadListStore::new(name, &data)?;
|
let mut list = DownloadListStore::new(name, &data)?;
|
||||||
|
|
||||||
list.set_description(description);
|
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 } => {
|
cli::ListCommand::Info { name } => {
|
||||||
let list = DownloadListStore::load(&name, &data)?;
|
let list = DownloadListStore::load(&name, &data)?;
|
||||||
|
|
||||||
println!("name: {}", list.name());
|
println!("name: {}", list.name());
|
||||||
println!("description: {}", list.description());
|
println!("description: {}", list.description());
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
cli::ListCommand::List => Ok(DownloadListStore::list(&data)?
|
cli::ListCommand::List => DownloadListStore::list(&data)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|n| println!("{n}"))),
|
.for_each(|n| println!("{n}")),
|
||||||
cli::ListCommand::Update { name, description } => {
|
cli::ListCommand::Update { name, description } => {
|
||||||
let mut list = DownloadListStore::load(&name, &data)?;
|
let mut list = DownloadListStore::load(&name, &data)?;
|
||||||
|
|
||||||
@ -46,10 +56,21 @@ fn main() -> anyhow::Result<()> {
|
|||||||
list.set_description(description);
|
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)> {
|
fn prepare_directories() -> anyhow::Result<(PathBuf, PathBuf)> {
|
||||||
|
63
src/state.rs
Normal file
63
src/state.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ impl DownloadListStore {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.context("failed to create list file")?;
|
.context(format!("failed to create list file: {}", path.display()))?;
|
||||||
|
|
||||||
file.lock_exclusive()
|
file.lock_exclusive()
|
||||||
.context("failed to acquire list file lock")?;
|
.context("failed to acquire list file lock")?;
|
||||||
@ -45,7 +45,7 @@ impl DownloadListStore {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.context("failed to open list file")?;
|
.context(format!("failed to open list file: {}", path.display()))?;
|
||||||
|
|
||||||
file.lock_exclusive()
|
file.lock_exclusive()
|
||||||
.context("failed to acquire list file lock")?;
|
.context("failed to acquire list file lock")?;
|
||||||
|
Loading…
Reference in New Issue
Block a user