kodik-rs 0.2.0

CLI tool for getting direct links to files from Kodik
use std::{
    fs::{self, File, OpenOptions},
    path::PathBuf,
    sync::LazyLock,
};

use kodik_parser::KODIK_STATE;
use serde::{Deserialize, Serialize};

pub static CACHE_PATH: LazyLock<Option<PathBuf>> =
    LazyLock::new(|| dirs::cache_dir().map(|cache_dir| cache_dir.join("kodik").join("cache.json")));

#[derive(Debug, Serialize, Deserialize)]
pub struct Cache {
    pub shift: Option<u8>,
    pub endpoint: Option<String>,
    #[serde(skip)]
    pub path: PathBuf,
}

impl Cache {
    pub fn load() -> Option<Self> {
        let cache_path = CACHE_PATH.as_ref()?;

        if !cache_path.exists() {
            if let Some(parent) = cache_path.parent() {
                let _ = fs::create_dir_all(parent);
            }
            let _ = File::create(cache_path);
        }

        match fs::read_to_string(cache_path) {
            Ok(content) => {
                let mut cache = serde_json::from_str::<Self>(&content).ok()?;
                cache.path.clone_from(cache_path);
                Some(cache)
            }
            Err(_) => None,
        }
    }

    pub fn save(&self) -> Option<()> {
        let cache_path = CACHE_PATH.as_ref()?;
        let file = OpenOptions::new().write(true).truncate(true).open(cache_path).ok()?;
        serde_json::to_writer_pretty(file, self).ok()
    }

    pub fn update(&mut self) {
        self.shift = Some(KODIK_STATE.shift());
        self.endpoint = Some(KODIK_STATE.endpoint().to_string());
    }

    pub fn is_changed(&self) -> bool {
        self.shift != Some(KODIK_STATE.shift()) || self.endpoint.as_deref() != Some(&*KODIK_STATE.endpoint())
    }

    pub fn apply(&self) {
        if let Some(shift) = self.shift {
            KODIK_STATE.set_shift(shift);
        }
        if let Some(endpoint) = self.endpoint.clone() {
            KODIK_STATE.set_endpoint(endpoint);
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
mod tests {
    use super::*;

    fn load_test() -> Cache {
        let mut cache = Cache::load().unwrap();

        if cache.endpoint.as_ref().unwrap().is_empty() || cache.shift.unwrap() == 0 {
            let cache_path = CACHE_PATH.as_ref().unwrap();
            cache = Cache {
                shift: Some(13),
                endpoint: Some(String::from("/abcd")),
                path: CACHE_PATH.as_ref().unwrap().to_owned(),
            };
            let file = OpenOptions::new().write(true).open(cache_path).unwrap();
            serde_json::to_writer_pretty(file, &cache).unwrap();
        }

        cache
    }

    #[test]
    fn apply_test() {
        let cache = load_test();
        assert!(KODIK_STATE.endpoint().is_empty());
        assert_eq!(KODIK_STATE.shift(), 0);
        cache.apply();
        assert!(!KODIK_STATE.endpoint().is_empty());
        assert_ne!(KODIK_STATE.shift(), 0);
    }
}