This commit is contained in:
@@ -0,0 +1,601 @@
|
||||
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<u64>,
|
||||
pub excluded_paths: Vec<String>,
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RemoteFileEntry {
|
||||
pub uri: String,
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub mtime_ms: i64,
|
||||
pub hash: Option<String>,
|
||||
pub is_dir: bool,
|
||||
pub file_id: Option<String>,
|
||||
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<String>,
|
||||
pub local_hash: Option<String>,
|
||||
pub remote_hash: Option<String>,
|
||||
pub local_mtime: Option<i64>,
|
||||
pub remote_mtime: Option<i64>,
|
||||
pub local_size: Option<u64>,
|
||||
pub remote_size: Option<u64>,
|
||||
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<i64>,
|
||||
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<String>,
|
||||
pub session_id: Option<String>,
|
||||
pub chunk_index: Option<u32>,
|
||||
}
|
||||
|
||||
#[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<u64>,
|
||||
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<String>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
// ===== 本地文件事件 =====
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LocalFileEvent {
|
||||
Created(Vec<PathBuf>),
|
||||
Modified(Vec<PathBuf>),
|
||||
Deleted(Vec<PathBuf>),
|
||||
Renamed { old_paths: Vec<PathBuf>, new_paths: Vec<PathBuf> },
|
||||
Moved { old_paths: Vec<PathBuf>, new_paths: Vec<PathBuf> },
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
// ===== API 分页响应 =====
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ListFilesResponse {
|
||||
pub files: Vec<RemoteFileEntry>,
|
||||
pub pagination: Pagination,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Pagination {
|
||||
pub next_page_token: Option<String>,
|
||||
pub is_cursor: bool,
|
||||
pub total: Option<u64>,
|
||||
}
|
||||
|
||||
// ===== 同步计划 =====
|
||||
|
||||
/// 重命名操作(本地触发 → 远程重命名,同目录下改文件名)
|
||||
#[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<SyncAction>,
|
||||
pub downloads: Vec<SyncAction>,
|
||||
pub delete_local: Vec<SyncAction>,
|
||||
pub delete_remote: Vec<SyncAction>,
|
||||
pub rename_remote: Vec<RenameAction>,
|
||||
pub move_remote: Vec<MoveAction>,
|
||||
pub rename_local: Vec<LocalRenameAction>,
|
||||
pub move_local: Vec<LocalRenameAction>,
|
||||
pub conflicts: Vec<SyncConflict>,
|
||||
pub mkdirs_local: Vec<String>,
|
||||
pub mkdirs_remote: Vec<String>,
|
||||
/// 需要递归扫描的顶层目录(持续同步场景,目录整体提交给 Worker)
|
||||
pub scan_dirs: Vec<String>,
|
||||
}
|
||||
|
||||
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<LocalFileEntry>,
|
||||
pub remote_entry: Option<RemoteFileEntry>,
|
||||
pub db_mapping: Option<FileMapping>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SyncConflict {
|
||||
pub relative_path: String,
|
||||
pub conflict_type: ConflictType,
|
||||
pub local_entry: Option<LocalFileEntry>,
|
||||
pub remote_entry: Option<RemoteFileEntry>,
|
||||
pub db_mapping: Option<FileMapping>,
|
||||
}
|
||||
|
||||
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<u64>,
|
||||
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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<String>,
|
||||
}
|
||||
|
||||
/// 单文件操作记录 — 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<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 任务项查询过滤器
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TaskItemFilter {
|
||||
pub task_id: Option<String>,
|
||||
pub relative_path_contains: Option<String>,
|
||||
pub action_type: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub limit: u32,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
// ===== 云端相册检查结果 =====
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CloudAlbumCheckResult {
|
||||
pub dcim_exists: bool,
|
||||
pub pictures_exists: bool,
|
||||
pub dcim_uri: Option<String>,
|
||||
pub pictures_uri: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user