mod initial_sync; mod continuous_sync; mod local_events; mod remote_events; mod album; #[cfg(feature = "windows-cfapi")] mod wcf; // 非 WCF feature 下的 stub 方法,供 remote_events.rs 编译通过 #[cfg(not(feature = "windows-cfapi"))] impl SyncEngine { async fn _create_placeholder_for_remote( &self, _relative: &str, _remote: &RemoteFileEntry, _local_root: &std::path::Path, _root_id: &str, ) { // MirrorWcf 模式在非 Windows 平台不可用,此方法不应被调用 } } use crate::api_client::ApiClient; use crate::conflict_resolver::ConflictResolver; use crate::errors::Result; use crate::event_sink::EventSink; use crate::file_lock::FileLockRegistry; use crate::models::*; use crate::sync_db::SyncDb; use crate::worker::WorkerPool; use dashmap::DashMap; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; #[cfg(feature = "windows-cfapi")] use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; pub struct SyncEngine { state: RwLock, db: Arc, api: Arc, config: RwLock, conflict: RwLock, sync_root_id: Option, shutdown_token: std::sync::Mutex, /// 同步操作互斥锁:防止 force_sync / run_initial_sync 并发 sync_lock: tokio::sync::Mutex<()>, worker_pool: WorkerPool, #[allow(dead_code)] file_locks: Arc, #[allow(dead_code)] ensured_dirs: Arc>, event_sink: Arc, /// 远程操作导致的本地路径变化,抑制本地 debouncer 自触发事件 suppress_paths: Arc>, /// WCF 平台适配器(仅 MirrorWcf 模式下初始化) #[cfg(feature = "windows-cfapi")] platform_adapter: std::sync::Mutex>>, /// WCF FETCH_DATA 回调接收端(在适配器初始化时提取) #[cfg(feature = "windows-cfapi")] wcf_fetch_rx: std::sync::Mutex>>, /// WCF 水合缓存:uri → 已下载的完整文件数据,避免同一文件重复下载 #[cfg(feature = "windows-cfapi")] hydration_cache: Arc, std::time::Instant)>>, /// 缓存的本地同步根路径(WCF 清理时同步读取,避免 await) #[cfg(feature = "windows-cfapi")] cached_local_root: std::sync::Mutex, } impl SyncEngine { pub async fn new(config: SyncConfig) -> Result { let db_path = config.data_dir.join("sync_core").join("datas").join(".sync_db.sqlite3"); let db_path_clone = db_path.clone(); let db = Arc::new(tokio::task::spawn_blocking(move || SyncDb::open(&db_path_clone)).await??); let api = Arc::new(ApiClient::new(&config.base_url, &config.access_token, &config.refresh_token, &config.client_id)); let conflict = ConflictResolver::new(config.conflict_strategy.clone()); let sync_root_id = match db.upsert_sync_root(&config).await { Ok(id) => Some(id), Err(e) => { tracing::warn!("写入 sync_root 失败: {}", e); None } }; let shutdown_token = CancellationToken::new(); let file_locks = Arc::new(FileLockRegistry::new()); let ensured_dirs = Arc::new(DashMap::new()); let event_sink = Arc::new(EventSink::new()); let suppress_paths = Arc::new(DashMap::new()); let max_workers = config.max_workers; let client_id = config.client_id.clone(); let worker_pool = WorkerPool::new( db.clone(), api.clone(), file_locks.clone(), ensured_dirs.clone(), event_sink.clone(), shutdown_token.clone(), max_workers, &client_id, ); Ok(Self { state: RwLock::new(SyncState::Idle), db, api, config: RwLock::new(config), conflict: RwLock::new(conflict), sync_root_id, shutdown_token: std::sync::Mutex::new(shutdown_token), sync_lock: tokio::sync::Mutex::new(()), worker_pool, file_locks, ensured_dirs, event_sink, suppress_paths, #[cfg(feature = "windows-cfapi")] platform_adapter: std::sync::Mutex::new(None), #[cfg(feature = "windows-cfapi")] wcf_fetch_rx: std::sync::Mutex::new(None), #[cfg(feature = "windows-cfapi")] hydration_cache: Arc::new(DashMap::new()), #[cfg(feature = "windows-cfapi")] cached_local_root: std::sync::Mutex::new(std::path::PathBuf::new()), }) } async fn snapshot_worker_config(&self) -> WorkerConfig { let config = self.config.read().await; WorkerConfig { local_root: config.local_root.clone(), remote_root: config.remote_root.clone(), max_concurrent_transfers: config.max_concurrent_transfers, bandwidth_limit: config.bandwidth_limit, conflict_strategy: config.conflict_strategy.clone(), wcf_delete_mode: config.wcf_delete_mode.clone(), sync_root_id: self.sync_root_id.clone().unwrap_or_default(), sync_mode: config.sync_mode.clone(), } } /// 确保 shutdown token 未被取消(stop 后重新启动时使用) pub fn ensure_token_fresh(&self) { let token = self.shutdown_token.lock().unwrap().clone(); if token.is_cancelled() { let new_token = tokio_util::sync::CancellationToken::new(); self.worker_pool.update_shutdown_token(new_token.clone()); *self.shutdown_token.lock().unwrap() = new_token; } } pub async fn stop(&self) -> Result<()> { self.shutdown_token.lock().unwrap().cancel(); *self.state.write().await = SyncState::Stopped; Ok(()) } pub async fn pause(&self) -> Result<()> { *self.state.write().await = SyncState::Paused; Ok(()) } pub async fn resume(&self) -> Result<()> { *self.state.write().await = SyncState::Continuous; Ok(()) } pub async fn force_sync(&self) -> Result { // 取消当前所有操作(持续同步 + 正在运行的初始同步) self.shutdown_token.lock().unwrap().cancel(); // 创建新 token,供接下来的 run_initial_sync 使用 let new_token = tokio_util::sync::CancellationToken::new(); *self.shutdown_token.lock().unwrap() = new_token.clone(); self.worker_pool.update_shutdown_token(new_token); // run_initial_sync 会等待 sync_lock(旧同步的 worker 检测到取消后快速退出,释放锁) self.run_initial_sync().await } /// 重置同步:停止任务 → 清空 DB → 清空本地目录 → 回到初始状态 pub async fn reset_sync(&self) -> Result<()> { tracing::info!("开始重置同步..."); // 1. 停止同步 self.stop().await?; // 2. 清理 WCF(重置时需要彻底清理) #[cfg(feature = "windows-cfapi")] { self.cleanup_wcf(); } // 3. 终止所有活跃 Worker // 2. 终止所有活跃 Worker self.worker_pool.abort_all_workers().await; // 3. 清空 DB 业务数据 self.db.reset_sync_data().await?; tracing::info!("同步数据库已清空"); // 4. 清空本地同步目录(保留目录本身,只删内容) let local_root = self.config.read().await.local_root.clone(); if local_root.exists() { let entries = std::fs::read_dir(&local_root) .map_err(|_| crate::errors::SyncError::DiskFull { needed: 0, available: 0 })?; for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { let _ = std::fs::remove_dir_all(&path); } else { let _ = std::fs::remove_file(&path); } } tracing::info!("本地同步目录已清空: {}", local_root.display()); } // 5. 清空内存缓存 self.ensured_dirs.clear(); self.suppress_paths.clear(); // 6. 重置状态 *self.state.write().await = SyncState::Idle; tracing::info!("同步重置完成,已回到初始状态"); Ok(()) } pub fn status(&self) -> SyncStatusSnapshot { let state = self.state.try_read().map(|g| g.clone()).unwrap_or(SyncState::Idle); let (synced_files, total_files) = match &state { SyncState::InitialSync { progress } => { let done = progress.uploaded + progress.downloaded; (done, progress.total_to_sync) } _ => (0, 0), }; SyncStatusSnapshot { state, synced_files, total_files, uploading_count: 0, downloading_count: 0, conflict_count: 0, error_count: 0, last_sync_time: None, error_message: None, } } pub fn active_worker_count(&self) -> u32 { self.worker_pool.active_worker_count() as u32 } pub async fn config(&self) -> SyncConfig { self.config.read().await.clone() } pub async fn update_config(&self, new_config: SyncConfig) -> Result<()> { let old_access_token = { let config = self.config.read().await; config.access_token.clone() }; *self.conflict.write().await = ConflictResolver::new(new_config.conflict_strategy.clone()); if new_config.access_token != old_access_token { self.api.update_token(new_config.access_token.clone()).await; } let new_bandwidth = new_config.bandwidth_limit; let new_conflict = format!("{:?}", new_config.conflict_strategy); let new_wcf_delete = format!("{:?}", new_config.wcf_delete_mode); let new_mode = format!("{:?}", new_config.sync_mode); let new_max_concurrent = new_config.max_concurrent_transfers; *self.config.write().await = new_config; if new_bandwidth.is_some() { tracing::info!("仅对下载限速生效, 由于Cloudreve实现原因, 上传限速无法生效"); } tracing::info!( "同步配置已更新: 模式={}, 冲突策略={}, WCF删除={}, 并发={}, 带宽限制={:?}", new_mode, new_conflict, new_wcf_delete, new_max_concurrent, new_bandwidth ); Ok(()) } pub async fn update_access_token(&self, token: String) { self.api.update_token(token).await; } pub async fn register_event_sink(&self, sink: crate::frb_generated::StreamSink) { self.event_sink.register(sink).await; } pub async fn get_active_tasks(&self) -> Result> { self.db.get_active_sync_tasks().await } pub async fn get_recent_tasks(&self, limit: u32) -> Result> { self.db.get_recent_sync_tasks(limit).await } pub async fn get_task_detail(&self, task_id: &str) -> Result> { self.db.get_sync_task_items(task_id).await } pub async fn query_task_items(&self, filter: &TaskItemFilter) -> Result> { self.db.query_task_items(filter).await } pub async fn hydrate_file(&self, local_path: &str) -> Result<()> { #[cfg(feature = "windows-cfapi")] { let path = std::path::PathBuf::from(local_path); if let Some(adapter) = self.platform_adapter.lock().unwrap().as_ref() { adapter.hydrate_file(&path)?; } } let _ = local_path; Ok(()) } pub async fn shutdown(self) -> Result<()> { self.stop().await } async fn load_all_mappings(&self) -> Result> { let root_id = match &self.sync_root_id { Some(id) => id.clone(), None => return Ok(HashMap::new()), }; let pool = self.db.read_pool(); let result = tokio::task::spawn_blocking(move || -> Result> { let conn = pool.get()?; let mut stmt = conn.prepare( "SELECT id, sync_root_id, local_path, remote_uri, remote_file_id, local_hash, remote_hash, local_mtime, remote_mtime, local_size, remote_size, sync_status, is_placeholder FROM file_mapping WHERE sync_root_id = ?1" )?; let mappings: HashMap = stmt.query_map( rusqlite::params![root_id], |row| { let local_path: String = row.get(2)?; Ok(( crate::utils::normalize_path(&local_path), FileMapping { id: row.get(0)?, sync_root_id: row.get(1)?, local_path: std::path::PathBuf::from(local_path), remote_uri: row.get(3)?, remote_file_id: row.get(4)?, local_hash: row.get(5)?, remote_hash: row.get(6)?, local_mtime: row.get(7)?, remote_mtime: row.get(8)?, local_size: row.get(9)?, remote_size: row.get(10)?, sync_status: crate::diff::parse_sync_status_from_str(&row.get::<_, String>(11)?), is_placeholder: row.get::<_, i32>(12)? != 0, }, )) }, )?.filter_map(|r| r.ok()).collect(); Ok(mappings) }).await??; Ok(result) } }