mwkeep 0.0.1

A tool for house keeping MediaWiki sites.
//! Application state type

use std::{
    collections::HashMap,
    fs::{self, File},
    io::{self, BufRead, BufReader, BufWriter, Write},
    path::Path,
};

use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

use crate::deser;

/// State type for each wiki instances
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Instance {
    last_edit_time: OffsetDateTime,
}

impl Instance {
    pub fn new() -> Self {
        Self {
            last_edit_time: OffsetDateTime::UNIX_EPOCH,
        }
    }

    pub fn last_edit_time(&self) -> OffsetDateTime {
        self.last_edit_time
    }

    pub fn update_last_edit_time(&mut self, now: OffsetDateTime) {
        self.last_edit_time = now;
    }
}

/// Persistent state holder for the application
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct State {
    instance: HashMap<String, Instance>,
}

impl State {
    pub fn new(domains: &Vec<String>) -> Self {
        let instance = Instance::new();
        let mut map = HashMap::new();
        for domain in domains {
            if map.insert(domain.clone(), instance.clone()).is_some() {
                panic!("duplicated domains")
            }
        }
        Self { instance: map }
    }

    pub fn instance(&mut self, domain: &str) -> Option<&mut Instance> {
        self.instance.get_mut(domain)
    }

    pub fn new_instance(&mut self, domain: &str) -> Option<&mut Instance> {
        let instance = Instance::new();
        if self.instance.insert(domain.to_string(), instance).is_some() {
            panic!("new instance already exists")
        }
        self.instance(domain)
    }

    pub fn load(path: &Path) -> Result<State, deser::Error> {
        let f = File::open(path)?;
        let reader = BufReader::new(f);
        let lines = reader.lines().collect::<io::Result<Vec<String>>>()?;
        let text = lines.join("\n");
        Ok(toml::from_str(&text)?)
    }

    pub fn save(&self, path: &Path) -> Result<(), deser::Error> {
        let string = toml::to_string(self)?;

        let filename = path.file_name();
        if filename.is_none() {
            panic!("Filename not found: {:?}", path)
        }
        let filename = filename.unwrap().to_str();
        if filename.is_none() {
            panic!("Invalid filename: {:?}", path)
        }
        let mut new_path = path.to_path_buf();
        if !new_path.pop() {
            panic!("Parent directory not found: {:?}", path)
        }
        new_path.push(filename.unwrap().to_string() + ".new");

        let f = File::create(new_path.clone())?;
        let mut writer = BufWriter::new(f);
        writer.write_all(string.as_bytes())?;
        writer.flush()?;

        fs::rename(new_path, path)?;

        Ok(())
    }
}