use crate::{
layout::{widget::WidgetType, Layout},
todo::{ToDo, ToDoState},
};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct UIState {
pub active: WidgetType,
pub todo_state: ToDoState,
}
impl UIState {
pub fn new(layout: &Layout, todo: &ToDo) -> Self {
Self {
active: layout.get_active_widget(),
todo_state: todo.get_state().clone(),
}
}
pub fn save(&self, path: &Path) -> Result<()> {
self.serialize(&mut File::create(path).with_context(|| format!("{path:?}"))?)
}
fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(toml::to_string_pretty(&self).unwrap().as_bytes())?;
Ok(())
}
pub fn load(path: &Path) -> Result<Self> {
let file = File::open(path).with_context(|| format!("{path:?}"))?;
Ok(UIState::deserialize(file))
}
fn deserialize<R: Read>(mut reader: R) -> Self {
let mut buf = String::default();
if let Err(e) = reader.read_to_string(&mut buf) {
log::error!("Cannot load UI state, invalid UTF-8: {}", e);
return Self::default();
}
match toml::from_str(buf.as_str()) {
Ok(c) => c,
Err(e) => {
log::error!("Cannot parse UI state: {}", e);
Self::default()
}
}
}
}
#[cfg(test)]
mod tests {
use crate::config::Config;
use super::*;
use std::{io::BufWriter, path::PathBuf};
use test_log::test;
#[test]
fn non_existing_state_file() {
let path = PathBuf::from("/this/path/does/not/exists");
let err = UIState::load(&path).expect_err("This file should not exist.");
assert!(err.to_string().contains("does/not/exists"), "{err}");
let err = UIState::default()
.save(&path)
.expect_err("This file should not exist.");
assert!(err.to_string().contains("does/not/exists"), "{err}");
}
#[test]
fn de_serialize() -> Result<()> {
let layout = Layout::from_str("[ Done, ]", &ToDo::default(), &Config::default()).unwrap();
let state = UIState::new(&layout, &ToDo::default());
let mut writer = BufWriter::new(Vec::new());
state.serialize(&mut writer)?;
let deserialized = UIState::deserialize(writer.into_inner().unwrap().as_slice());
assert_ne!(deserialized, UIState::default());
assert_eq!(deserialized.active, WidgetType::Done);
Ok(())
}
#[test]
fn deserialize_failed() {
let s = String::from("invalid toml");
assert_eq!(UIState::deserialize(s.as_bytes()), UIState::default());
let invalid_utf8: Vec<u8> = vec![0xf0, 0x28, 0x8c, 0x28];
assert_eq!(
UIState::deserialize(invalid_utf8.as_slice()),
UIState::default()
);
}
}