use serde::{Deserialize, Serialize}; use std::path::PathBuf; // ===== 同步状态 ===== #[derive(Debug, Clone, PartialEq)] pub enum SyncState { Idle, Initializing, InitialSync { progress: InitialSyncProgress }, Continuous, Paused, Error { message: String }, Stopped, } #[derive(Debug, Clone, Default, PartialEq)] pub struct InitialSyncProgress { pub scanned_local: u64, pub scanned_remote: u64, pub uploaded: u64, pub downloaded: u64, pub conflicts: u64, pub total_to_sync: u64, } // ===== 同步配置 ===== #[derive(Debug, Clone)] pub struct SyncConfig { pub base_url: String, pub access_token: String, pub refresh_token: String, pub local_root: PathBuf, pub remote_root: String, pub sync_mode: SyncMode, pub conflict_strategy: ConflictStrategy, pub wcf_delete_mode: WcfDeleteMode, pub max_concurrent_transfers: usize, pub bandwidth_limit: Option, pub excluded_paths: Vec, pub max_workers: usize, pub data_dir: PathBuf, pub client_id: String, } #[derive(Debug, Clone, PartialEq)] pub enum SyncMode { Full, UploadOnly, DownloadOnly, Album, MirrorWcf, } // ===== 文件条目 ===== #[derive(Debug, Clone)] pub struct LocalFileEntry { pub relative_path: PathBuf, pub size: u64, pub mtime_ms: i64, pub quick_hash: String, pub is_dir: bool, pub mime_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RemoteFileEntry { pub uri: String, pub name: String, pub size: u64, pub mtime_ms: i64, pub hash: Option, pub is_dir: bool, pub file_id: Option, pub path: String, pub created_at_ms: i64, } // ===== 文件映射 ===== #[derive(Debug, Clone)] pub struct FileMapping { pub id: i64, pub sync_root_id: String, pub local_path: PathBuf, pub remote_uri: String, pub remote_file_id: Option, pub local_hash: Option, pub remote_hash: Option, pub local_mtime: Option, pub remote_mtime: Option, pub local_size: Option, pub remote_size: Option, pub sync_status: SyncFileStatus, pub is_placeholder: bool, } #[derive(Debug, Clone, PartialEq)] pub enum SyncFileStatus { Synced, Uploading, Downloading, Conflict, Placeholder, } // ===== 冲突 ===== #[derive(Debug, Clone, PartialEq)] pub enum ConflictType { BothModified, DeleteVsModify, NameCollision, TypeMismatch, } #[derive(Debug, Clone, PartialEq)] pub enum ConflictStrategy { KeepLocal, KeepRemote, KeepBoth, NewestWins, LargestWins, Manual, } /// 镜像同步(WCF)本地删除模式 — 仅 MirrorWcf 模式下生效 #[derive(Debug, Clone, PartialEq)] pub enum WcfDeleteMode { /// 仅删除本地,保留远程(可重新水合) LocalOnly, /// 同步删除远程副本 SyncRemote, } #[derive(Debug, Clone)] pub enum ConflictResolution { UploadLocal, DownloadRemote, RenameLocal { new_name: String }, DeleteLocal, DeleteRemote, MarkManual, } // ===== 传输 ===== #[derive(Debug, Clone)] pub struct TransferTask { pub id: i64, pub sync_root_id: String, pub file_mapping_id: Option, pub direction: TransferDirection, pub local_path: PathBuf, pub remote_uri: String, pub file_size: u64, pub bytes_done: u64, pub status: TransferStatus, pub retry_count: u32, pub max_retries: u32, pub error_message: Option, pub session_id: Option, pub chunk_index: Option, } #[derive(Debug, Clone, PartialEq)] pub enum TransferDirection { Upload, Download, } #[derive(Debug, Clone, PartialEq)] pub enum TransferStatus { Pending, Active, Paused, Completed, Failed, } #[derive(Debug, Clone)] pub struct TransferConfig { pub max_concurrent: usize, pub max_retries: u32, pub retry_base_delay_ms: u64, pub retry_max_delay_ms: u64, pub bandwidth_limit: Option, pub disk_space_reserve: u64, } impl Default for TransferConfig { fn default() -> Self { Self { max_concurrent: 3, max_retries: 5, retry_base_delay_ms: 1000, retry_max_delay_ms: 60000, bandwidth_limit: None, disk_space_reserve: 1024 * 1024 * 1024, } } } // ===== 同步摘要 ===== #[derive(Debug, Clone, Default)] pub struct SyncSummary { pub uploaded: u32, pub downloaded: u32, pub renamed: u32, pub moved: u32, pub conflicts: u32, pub failed: u32, pub skipped: u32, pub deleted_local: u32, pub deleted_remote: u32, pub duration_ms: u64, } // ===== 同步状态快照 ===== #[derive(Debug, Clone)] pub struct SyncStatusSnapshot { pub state: SyncState, pub synced_files: u64, pub total_files: u64, pub uploading_count: u32, pub downloading_count: u32, pub conflict_count: u32, pub error_count: u32, pub last_sync_time: Option, pub error_message: Option, } // ===== 本地文件事件 ===== #[derive(Debug, Clone)] pub enum LocalFileEvent { Created(Vec), Modified(Vec), Deleted(Vec), Renamed { old_paths: Vec, new_paths: Vec }, Moved { old_paths: Vec, new_paths: Vec }, } impl LocalFileEvent { pub fn paths(&self) -> &[PathBuf] { match self { LocalFileEvent::Created(p) => p, LocalFileEvent::Modified(p) => p, LocalFileEvent::Deleted(p) => p, LocalFileEvent::Renamed { .. } | LocalFileEvent::Moved { .. } => &[], } } } // ===== 远程文件事件 ===== #[derive(Debug, Clone)] pub enum RemoteFileEvent { Created(RemoteFileEntry), Modified(RemoteFileEntry), Deleted { uri: String, name: String }, Renamed { old_uri: String, new_entry: RemoteFileEntry }, Moved { old_uri: String, new_entry: RemoteFileEntry }, } // ===== 平台回调事件 (Windows CFApi) ===== #[derive(Debug, Clone)] pub enum PlatformCallbackEvent { HydrateRequested { local_path: String, transfer_key: i64, }, } // ===== 上传会话 ===== #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UploadSession { pub session_id: String, pub chunk_size: u64, /// 远程存储的上传 URL 列表(每个分片一个 URL) /// 仅当 storage_policy_type != "local" 时有值 pub upload_urls: Vec, /// 存储策略类型:local / onedrive / s3 / oss / cos / etc. pub storage_policy_type: String, /// 回调密钥(远程存储上传完成后需要回调通知服务端) pub callback_secret: String, /// 上传文件名(仅用于日志标识,并发时区分不同文件) pub file_name: String, } impl UploadSession { /// 是否使用远程存储直接上传(非本地策略) pub fn is_remote_storage(&self) -> bool { self.storage_policy_type != "local" && !self.upload_urls.is_empty() } /// 获取第 index 个分片的上传 URL /// 远程存储:返回 upload_urls[index](不足则用最后一个) /// 本地存储:返回 None,由调用方拼接 /file/upload/{session_id}/{index} pub fn chunk_upload_url(&self, index: usize) -> Option<&str> { if !self.is_remote_storage() { return None; } if self.upload_urls.is_empty() { return None; } Some(self.upload_urls[index.min(self.upload_urls.len() - 1)].as_str()) } } // ===== API 分页响应 ===== #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ListFilesResponse { pub files: Vec, pub pagination: Pagination, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Pagination { pub next_page_token: Option, pub is_cursor: bool, pub total: Option, } // ===== 同步计划 ===== /// 重命名操作(本地触发 → 远程重命名,同目录下改文件名) #[derive(Debug, Clone)] pub struct RenameAction { pub old_relative_path: String, pub new_relative_path: String, pub remote_uri: String, pub new_name: String, } /// 移动操作(本地触发 → 远程移动,跨目录) #[derive(Debug, Clone)] pub struct MoveAction { pub old_relative_path: String, pub new_relative_path: String, pub remote_uri: String, pub dst_remote_dir_uri: String, } /// 本地重命名/移动操作(远程触发 → 本地执行) #[derive(Debug, Clone)] pub struct LocalRenameAction { pub old_relative_path: String, pub new_relative_path: String, pub new_remote_uri: String, } #[derive(Debug, Clone, Default)] pub struct SyncPlan { pub uploads: Vec, pub downloads: Vec, pub delete_local: Vec, pub delete_remote: Vec, pub rename_remote: Vec, pub move_remote: Vec, pub rename_local: Vec, pub move_local: Vec, pub conflicts: Vec, pub mkdirs_local: Vec, pub mkdirs_remote: Vec, /// 需要递归扫描的顶层目录(持续同步场景,目录整体提交给 Worker) pub scan_dirs: Vec, } impl SyncPlan { /// 判断是否有实际操作需要执行(含 scan_dirs 则为非空,因为扫描可能发现新文件) pub fn has_work(&self) -> bool { !self.uploads.is_empty() || !self.downloads.is_empty() || !self.delete_local.is_empty() || !self.delete_remote.is_empty() || !self.rename_remote.is_empty() || !self.move_remote.is_empty() || !self.rename_local.is_empty() || !self.move_local.is_empty() || !self.conflicts.is_empty() || !self.scan_dirs.is_empty() } } #[derive(Debug, Clone)] pub struct SyncAction { pub relative_path: String, pub local_entry: Option, pub remote_entry: Option, pub db_mapping: Option, } #[derive(Debug, Clone)] pub struct SyncConflict { pub relative_path: String, pub conflict_type: ConflictType, pub local_entry: Option, pub remote_entry: Option, pub db_mapping: Option, } impl SyncPlan { pub fn total_actions(&self) -> u64 { self.uploads.len() as u64 + self.downloads.len() as u64 + self.delete_local.len() as u64 + self.delete_remote.len() as u64 + self.rename_remote.len() as u64 + self.move_remote.len() as u64 } } // ===== Worker / Task 类型 ===== /// Worker 配置快照 — 创建时一次性读取,运行中不读任何锁 #[derive(Debug, Clone)] pub struct WorkerConfig { pub local_root: PathBuf, pub remote_root: String, pub max_concurrent_transfers: usize, pub bandwidth_limit: Option, pub conflict_strategy: ConflictStrategy, pub wcf_delete_mode: WcfDeleteMode, pub sync_root_id: String, pub sync_mode: SyncMode, } /// Worker 触发来源 #[derive(Debug, Clone, PartialEq)] pub enum WorkerTrigger { InitialSync, Continuous, Manual, } impl WorkerTrigger { pub fn as_str(&self) -> &'static str { match self { WorkerTrigger::InitialSync => "initial_sync", WorkerTrigger::Continuous => "continuous", WorkerTrigger::Manual => "manual", } } } /// Worker 级别状态 #[derive(Debug, Clone, PartialEq)] pub enum WorkerStatus { Pending, Running, Completed, Failed, Cancelled, } impl WorkerStatus { pub fn as_str(&self) -> &'static str { match self { WorkerStatus::Pending => "pending", WorkerStatus::Running => "running", WorkerStatus::Completed => "completed", WorkerStatus::Failed => "failed", WorkerStatus::Cancelled => "cancelled", } } } impl std::str::FromStr for WorkerStatus { type Err = (); fn from_str(s: &str) -> std::result::Result { match s { "pending" => Ok(WorkerStatus::Pending), "running" => Ok(WorkerStatus::Running), "completed" => Ok(WorkerStatus::Completed), "failed" => Ok(WorkerStatus::Failed), "cancelled" => Ok(WorkerStatus::Cancelled), _ => Err(()), } } } /// 单文件操作状态 #[derive(Debug, Clone, PartialEq)] pub enum TaskItemStatus { Pending, Running, Completed, Failed, Skipped, } impl TaskItemStatus { pub fn as_str(&self) -> &'static str { match self { TaskItemStatus::Pending => "pending", TaskItemStatus::Running => "running", TaskItemStatus::Completed => "completed", TaskItemStatus::Failed => "failed", TaskItemStatus::Skipped => "skipped", } } } impl std::str::FromStr for TaskItemStatus { type Err = (); fn from_str(s: &str) -> std::result::Result { match s { "pending" => Ok(TaskItemStatus::Pending), "running" => Ok(TaskItemStatus::Running), "completed" => Ok(TaskItemStatus::Completed), "failed" => Ok(TaskItemStatus::Failed), "skipped" => Ok(TaskItemStatus::Skipped), _ => Err(()), } } } /// 操作类型 — 精确记录每个文件的操作 #[derive(Debug, Clone, PartialEq)] pub enum TaskActionType { Upload, Download, DeleteLocal, DeleteRemote, Rename, Move, MkdirRemote, MkdirLocal, ConflictResolve, CreatePlaceholder, } impl TaskActionType { pub fn as_str(&self) -> &'static str { match self { TaskActionType::Upload => "upload", TaskActionType::Download => "download", TaskActionType::DeleteLocal => "delete_local", TaskActionType::DeleteRemote => "delete_remote", TaskActionType::Rename => "rename", TaskActionType::Move => "move", TaskActionType::MkdirRemote => "mkdir_remote", TaskActionType::MkdirLocal => "mkdir_local", TaskActionType::ConflictResolve => "conflict_resolve", TaskActionType::CreatePlaceholder => "create_placeholder", } } } impl std::str::FromStr for TaskActionType { type Err = (); fn from_str(s: &str) -> std::result::Result { match s { "upload" => Ok(TaskActionType::Upload), "download" => Ok(TaskActionType::Download), "delete_local" => Ok(TaskActionType::DeleteLocal), "delete_remote" => Ok(TaskActionType::DeleteRemote), "rename" => Ok(TaskActionType::Rename), "move" => Ok(TaskActionType::Move), "mkdir_remote" => Ok(TaskActionType::MkdirRemote), "mkdir_local" => Ok(TaskActionType::MkdirLocal), "conflict_resolve" => Ok(TaskActionType::ConflictResolve), "create_placeholder" => Ok(TaskActionType::CreatePlaceholder), _ => Err(()), } } } /// Worker 级别的任务摘要 — DB 持久化,供 UI 查询 #[derive(Debug, Clone)] pub struct SyncTask { pub id: String, pub trigger: WorkerTrigger, pub total_count: u32, pub completed_count: u32, pub failed_count: u32, pub status: WorkerStatus, pub created_at: String, pub updated_at: String, pub finished_at: Option, } /// 单文件操作记录 — DB 持久化,支持多维度查询 #[derive(Debug, Clone)] pub struct SyncTaskItem { pub id: i64, pub task_id: String, pub relative_path: String, pub action_type: TaskActionType, pub status: TaskItemStatus, pub file_size: u64, pub error_message: Option, pub created_at: String, pub updated_at: String, } /// 任务项查询过滤器 #[derive(Debug, Clone, Default)] pub struct TaskItemFilter { pub task_id: Option, pub relative_path_contains: Option, pub action_type: Option, pub status: Option, pub limit: u32, pub offset: u32, } // ===== 云端相册检查结果 ===== #[derive(Debug, Clone)] pub struct CloudAlbumCheckResult { pub dcim_exists: bool, pub pictures_exists: bool, pub dcim_uri: Option, pub pictures_uri: Option, }