//! 阅读进度同步模块 //! //! 支持本地/云端进度同步、多设备同步等功能 use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; /// 阅读进度 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReadingProgress { /// 文档路径/ID pub document_id: String, /// 当前页码 pub current_page: usize, /// 总页数 pub total_pages: usize, /// 进度百分比 (0-100) pub percentage: f32, /// 最后阅读位置(字符偏移) pub position: usize, /// 最后阅读时间 pub last_read_at: DateTime, /// 设备标识 pub device_id: Option, /// 是否已同步 pub synced: bool, } impl ReadingProgress { pub fn new(document_id: String, total_pages: usize) -> Self { Self { document_id, current_page: 1, total_pages, percentage: 0.0, position: 0, last_read_at: Utc::now(), device_id: None, synced: false, } } /// 更新进度 pub fn update(&mut self, page: usize, position: usize) { self.current_page = page; self.position = position; self.percentage = if self.total_pages > 0 { (page as f32 / self.total_pages as f32) * 100.0 } else { 0.0 }; self.last_read_at = Utc::now(); } /// 标记为已同步 pub fn mark_synced(&mut self) { self.synced = true; } } /// 进度同步管理器 pub struct ProgressManager { db: sled::Db, device_id: String, } impl ProgressManager { /// 创建进度管理器 pub fn new(db_path: &str, device_id: Option) -> Result { let db = sled::open(db_path)?; let device_id = device_id.unwrap_or_else(|| Self::generate_device_id()); Ok(Self { db, device_id }) } /// 生成设备 ID fn generate_device_id() -> String { use std::time::{SystemTime, UNIX_EPOCH}; let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_millis(); format!("device_{}", timestamp) } /// 获取设备 ID pub fn get_device_id(&self) -> &str { &self.device_id } /// 保存进度 pub fn save_progress(&self, progress: &ReadingProgress) -> Result<()> { let key = format!("progress:{}", progress.document_id); let value = serde_json::to_vec(progress)?; self.db.insert(key, value)?; Ok(()) } /// 获取进度 pub fn get_progress(&self, document_id: &str) -> Result> { let key = format!("progress:{}", document_id); match self.db.get(key)? { Some(value) => { let progress: ReadingProgress = serde_json::from_slice(&value)?; Ok(Some(progress)) } None => Ok(None), } } /// 更新进度 pub fn update_progress( &self, document_id: &str, total_pages: usize, page: usize, position: usize, ) -> Result { let mut progress = match self.get_progress(document_id)? { Some(p) => p, None => ReadingProgress::new(document_id.to_string(), total_pages), }; progress.update(page, position); progress.device_id = Some(self.device_id.clone()); self.save_progress(&progress)?; Ok(progress) } /// 获取所有进度 pub fn get_all_progress(&self) -> Result> { let mut progress_list = Vec::new(); for item in self.db.scan_prefix("progress:") { let (_, value) = item?; let progress: ReadingProgress = serde_json::from_slice(&value)?; progress_list.push(progress); } // 按最后阅读时间排序 progress_list.sort_by(|a, b| b.last_read_at.cmp(&a.last_read_at)); Ok(progress_list) } /// 获取未同步的进度 pub fn get_unsynced_progress(&self) -> Result> { let mut unsynced = Vec::new(); for item in self.db.scan_prefix("progress:") { let (_, value) = item?; let mut progress: ReadingProgress = serde_json::from_slice(&value)?; if !progress.synced { unsynced.push(progress); } } Ok(unsynced) } /// 标记进度为已同步 pub fn mark_as_synced(&self, document_id: &str) -> Result<()> { if let Some(mut progress) = self.get_progress(document_id)? { progress.mark_synced(); self.save_progress(&progress)?; } Ok(()) } /// 合并远程进度(解决冲突) pub fn merge_remote_progress(&self, remote: &ReadingProgress) -> Result<()> { let local = self.get_progress(&remote.document_id)?; let should_update = match local { None => true, Some(local_progress) => { // 使用最新的进度 remote.last_read_at > local_progress.last_read_at } }; if should_update { let mut merged = remote.clone(); merged.device_id = Some(self.device_id.clone()); self.save_progress(&merged)?; } Ok(()) } /// 导出进度为 JSON pub fn export_json(&self) -> Result { let progress_list = self.get_all_progress()?; let json = serde_json::to_string_pretty(&progress_list)?; Ok(json) } /// 从 JSON 导入进度 pub fn import_json(&self, json: &str) -> Result<()> { let progress_list: Vec = serde_json::from_str(json)?; for progress in progress_list { self.save_progress(&progress)?; } Ok(()) } /// 删除进度 pub fn remove_progress(&self, document_id: &str) -> Result<()> { let key = format!("progress:{}", document_id); self.db.remove(key)?; Ok(()) } /// 清除所有进度 pub fn clear_all(&self) -> Result<()> { let keys: Vec> = self.db.scan_prefix("progress:").map(|item| { item.map(|(k, _)| k.to_vec()) }).collect::, _>>()?; for key in keys { self.db.remove(key)?; } Ok(()) } } /// 云端同步配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SyncConfig { /// 同步服务器 URL pub server_url: String, /// API 密钥 pub api_key: Option, /// 自动同步间隔(秒) pub auto_sync_interval: u64, /// 启用自动同步 pub auto_sync_enabled: bool, } impl Default for SyncConfig { fn default() -> Self { Self { server_url: String::new(), api_key: None, auto_sync_interval: 300, // 5 分钟 auto_sync_enabled: false, } } } /// 云端同步器 pub struct CloudSync { config: SyncConfig, progress_manager: ProgressManager, } impl CloudSync { /// 创建云端同步器 pub fn new(config: SyncConfig, progress_manager: ProgressManager) -> Self { Self { config, progress_manager, } } /// 同步到云端 pub fn sync_to_cloud(&self) -> Result<()> { if self.config.server_url.is_empty() { return Ok(()); // 未配置服务器,跳过 } let unsynced = self.progress_manager.get_unsynced_progress()?; if unsynced.is_empty() { return Ok(()); } let client = reqwest::blocking::Client::new(); for progress in unsynced { let url = format!("{}/api/v1/progress", self.config.server_url); let mut request = client .post(&url) .json(&progress); if let Some(api_key) = &self.config.api_key { request = request.header("Authorization", format!("Bearer {}", api_key)); } let response = request .send() .context("同步请求失败")?; if response.status().is_success() { self.progress_manager.mark_as_synced(&progress.document_id)?; } } Ok(()) } /// 从云端同步 pub fn sync_from_cloud(&self) -> Result<()> { if self.config.server_url.is_empty() { return Ok(()); } let client = reqwest::blocking::Client::new(); let url = format!("{}/api/v1/progress", self.config.server_url); let mut request = client.get(&url); if let Some(api_key) = &self.config.api_key { request = request.header("Authorization", format!("Bearer {}", api_key)); } let response = request .send() .context("获取云端进度失败")?; if !response.status().is_success() { return Ok(()); } let remote_progress_list: Vec = response.json() .context("解析云端进度失败")?; for remote in remote_progress_list { self.progress_manager.merge_remote_progress(&remote)?; } Ok(()) } /// 完全同步(双向) pub fn sync_all(&self) -> Result<()> { self.sync_to_cloud()?; self.sync_from_cloud()?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_progress_creation() { let mut progress = ReadingProgress::new("test_doc".to_string(), 100); assert_eq!(progress.current_page, 1); assert_eq!(progress.total_pages, 100); assert_eq!(progress.percentage, 1.0); progress.update(50, 1000); assert_eq!(progress.current_page, 50); assert!((progress.percentage - 50.0).abs() < 0.1); } #[test] fn test_device_id_generation() { let id1 = ProgressManager::generate_device_id(); let id2 = ProgressManager::generate_device_id(); // 两次生成的 ID 应该不同(因为时间戳不同) assert_ne!(id1, id2); } }