changxi 0.3.0

TUI EPUB Reader
use dirs::config_dir;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Bookmark {
    pub name: String,
    pub chapter_index: usize,
    pub scroll_position: usize,
    pub chapter_title: String,
    pub text_preview: String,
    pub created_at: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct BookProgress {
    pub current_chapter: usize,
    pub chapter_scrolls: HashMap<usize, usize>,
    #[serde(default)]
    pub title: String,
    #[serde(default)]
    pub author: String,
    #[serde(default)]
    pub path: String,
    #[serde(default)]
    pub last_read: u64,
    #[serde(default)]
    pub view_type: String,
    #[serde(default)]
    pub bookmarks: Vec<Bookmark>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ProgressStore {
    pub books: HashMap<String, BookProgress>,
    pub last_book_path: Option<String>,
    #[serde(skip)]
    pub path: PathBuf,
}

impl ProgressStore {
    pub fn load(profile_path: Option<PathBuf>) -> Self {
        let path = profile_path.unwrap_or_else(Self::get_default_path);
        let mut store = if let Ok(content) = fs::read_to_string(&path) {
            serde_json::from_str(&content).unwrap_or_default()
        } else {
            ProgressStore::default()
        };
        store.path = path;
        store
    }

    pub fn save(&self) -> Result<(), Box<dyn Error>> {
        if let Some(parent) = self.path.parent() {
            fs::create_dir_all(parent)?;
        }
        let content = serde_json::to_string_pretty(self)?;
        fs::write(&self.path, content)?;
        Ok(())
    }

    fn get_default_path() -> PathBuf {
        let mut path = config_dir().unwrap_or_else(|| PathBuf::from("."));
        path.push("changxi");
        path.push("progress.json");
        path
    }

    pub fn get_book(&self, book_id: &str) -> BookProgress {
        self.books.get(book_id).cloned().unwrap_or_default()
    }

    pub fn set_book(&mut self, book_id: String, progress: BookProgress) {
        self.books.insert(book_id, progress);
    }

    pub fn remove_book(&mut self, title: &str, author: &str) {
        // Find and remove the book by title and author
        let book_id = self
            .books
            .iter()
            .find(|(_, b)| b.title == title && b.author == author)
            .map(|(id, _)| id.clone());

        if let Some(id) = book_id {
            self.books.remove(&id);
        }
    }
}