cloud_disk_sync/report/
mod.rs

1use crate::config::SyncTask;
2use crate::error::SyncError;
3use crate::sync::diff::{DiffAction, FileDiff};
4use crate::utils::format_bytes;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::Path;
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct SyncReport {
12    pub task_id: String,
13    pub start_time: DateTime<Utc>,
14    pub end_time: Option<DateTime<Utc>>,
15    pub status: SyncStatus,
16    pub statistics: SyncStatistics,
17    pub files: Vec<FileSyncResult>,
18    pub errors: Vec<String>,
19    pub warnings: Vec<String>,
20    pub duration_seconds: i64,
21}
22
23impl SyncReport {
24    pub(crate) fn add_conflict(&mut self, diff_path: &String) {
25        self.statistics.conflicts += 1;
26        self.statistics.total_files += 1;
27        let mut result = FileSyncResult::new(diff_path.clone(), FileOperation::Verify);
28        result.status = FileSyncStatus::Conflict;
29        self.files.push(result);
30    }
31
32    pub(crate) fn add_success(&mut self, diff_path: &String, diff_size: i64) {
33        let mut result = FileSyncResult::new(diff_path.clone(), FileOperation::Upload);
34        result.status = FileSyncStatus::Success;
35        result.size = diff_size.abs() as u64;
36        result.transferred_size = diff_size.abs() as u64;
37
38        self.statistics.add_file_result(&result);
39        self.files.push(result);
40    }
41
42    pub(crate) fn add_failure(
43        &mut self,
44        diff_path: &String,
45        operation: FileOperation,
46        error: String,
47    ) {
48        let mut result = FileSyncResult::new(diff_path.clone(), operation);
49        result.status = FileSyncStatus::Failed;
50        result.error = Some(error.clone());
51
52        self.statistics.add_file_result(&result);
53        self.files.push(result);
54        // Add to errors list for summary
55        self.errors
56            .push(format!("Failed to process {}: {}", diff_path, error));
57    }
58}
59
60impl SyncReport {
61    pub fn new(task_id: &str) -> SyncReport {
62        SyncReport {
63            task_id: task_id.into(),
64            start_time: Default::default(),
65            end_time: None,
66            status: SyncStatus::Success,
67            statistics: SyncStatistics::default(),
68            files: vec![],
69            errors: vec![],
70            warnings: vec![],
71            duration_seconds: 0,
72        }
73    }
74    pub fn generate_html(&self) -> String {
75        format!(
76            r#"
77<!DOCTYPE html>
78<html>
79<head>
80    <title>Sync Report - {}</title>
81    <style>
82        body {{ font-family: Arial, sans-serif; margin: 40px; }}
83        .summary {{ background: #f5f5f5; padding: 20px; border-radius: 5px; }}
84        .stats {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }}
85        .stat-box {{ background: white; padding: 15px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
86        .success {{ color: green; }}
87        .error {{ color: red; }}
88        .warning {{ color: orange; }}
89        table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
90        th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
91        th {{ background-color: #4CAF50; color: white; }}
92        tr:nth-child(even) {{ background-color: #f2f2f2; }}
93    </style>
94</head>
95<body>
96    <h1>Sync Report: {}</h1>
97    <div class="summary">
98        <h2>Summary</h2>
99        <div class="stats">
100            <div class="stat-box">
101                <h3>Files Synced</h3>
102                <p class="success">{}</p>
103            </div>
104            <div class="stat-box">
105                <h3>Transfer Speed</h3>
106                <p>{:.2} MB/s</p>
107            </div>
108            <div class="stat-box">
109                <h3>Duration</h3>
110                <p>{:.1} seconds</p>
111            </div>
112        </div>
113    </div>
114
115    <h2>File Details</h2>
116    <table>
117        <thead>
118            <tr>
119                <th>File</th>
120                <th>Size</th>
121                <th>Status</th>
122                <th>Time</th>
123            </tr>
124        </thead>
125        <tbody>
126            {}
127        </tbody>
128    </table>
129
130    <h2>Errors ({})</h2>
131    <ul>
132        {}
133    </ul>
134</body>
135</html>
136        "#,
137            self.task_id,
138            self.task_id,
139            self.statistics.files_synced,
140            self.statistics.transfer_rate / 1024.0 / 1024.0,
141            self.statistics.duration_seconds,
142            self.generate_file_rows(),
143            self.errors.len(),
144            self.generate_error_list()
145        )
146    }
147
148    pub fn summary(&self) -> String {
149        self.statistics.summary()
150    }
151
152    fn generate_file_rows(&self) -> String {
153        let mut rows = String::new();
154        for f in &self.files {
155            rows.push_str(&format!(
156                "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
157                f.path,
158                f.human_readable_size(),
159                f.status.as_str(),
160                f.duration_ms.unwrap_or(0)
161            ));
162        }
163        rows
164    }
165
166    fn generate_error_list(&self) -> String {
167        let mut list = String::new();
168        for e in &self.errors {
169            list.push_str(&format!("<li>{}</li>", e));
170        }
171        list
172    }
173
174    pub fn generate_json(&self) -> String {
175        serde_json::to_string_pretty(self).unwrap_or_default()
176    }
177
178    pub fn save(&self) {}
179}
180
181/// 同步状态枚举
182#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
183pub enum SyncStatus {
184    /// 等待中(尚未开始)
185    Pending,
186    /// 运行中
187    Running,
188    /// 成功完成
189    Success,
190    /// 部分成功(有部分文件失败)
191    PartialSuccess,
192    /// 失败
193    Failed,
194    /// 已取消
195    Cancelled,
196    /// 正在重试
197    Retrying,
198    /// 暂停
199    Paused,
200    /// 验证中
201    Verifying,
202    /// 修复中
203    Repairing,
204}
205
206impl SyncStatus {
207    pub fn as_str(&self) -> &'static str {
208        match self {
209            Self::Pending => "待开始",
210            Self::Running => "运行中",
211            Self::Success => "成功",
212            Self::PartialSuccess => "部分成功",
213            Self::Failed => "失败",
214            Self::Cancelled => "已取消",
215            Self::Retrying => "重试中",
216            Self::Paused => "已暂停",
217            Self::Verifying => "验证中",
218            Self::Repairing => "修复中",
219        }
220    }
221
222    pub fn emoji(&self) -> &'static str {
223        match self {
224            Self::Pending => "⏳",
225            Self::Running => "🔄",
226            Self::Success => "✅",
227            Self::PartialSuccess => "⚠️",
228            Self::Failed => "❌",
229            Self::Cancelled => "🚫",
230            Self::Retrying => "🔄",
231            Self::Paused => "⏸️",
232            Self::Verifying => "🔍",
233            Self::Repairing => "🔧",
234        }
235    }
236
237    pub fn is_completed(&self) -> bool {
238        matches!(
239            self,
240            Self::Success | Self::PartialSuccess | Self::Failed | Self::Cancelled
241        )
242    }
243
244    pub fn is_successful(&self) -> bool {
245        matches!(self, Self::Success | Self::PartialSuccess)
246    }
247
248    pub fn can_retry(&self) -> bool {
249        matches!(self, Self::Failed | Self::PartialSuccess)
250    }
251
252    pub fn is_active(&self) -> bool {
253        matches!(
254            self,
255            Self::Running | Self::Retrying | Self::Verifying | Self::Repairing
256        )
257    }
258}
259
260/// 文件同步结果
261#[derive(Debug, Serialize, Deserialize)]
262pub struct FileSyncResult {
263    /// 文件路径
264    pub path: String,
265    /// 文件状态
266    pub status: FileSyncStatus,
267    /// 文件大小(字节)
268    pub size: u64,
269    /// 传输大小(实际传输的字节数,可能包括加密开销)
270    pub transferred_size: u64,
271    /// 源文件哈希
272    pub source_hash: Option<String>,
273    /// 目标文件哈希
274    pub target_hash: Option<String>,
275    /// 文件同步操作
276    pub operation: FileOperation,
277    /// 开始时间
278    pub start_time: Option<DateTime<Utc>>,
279    /// 结束时间
280    pub end_time: Option<DateTime<Utc>>,
281    /// 传输耗时(毫秒)
282    pub duration_ms: Option<u64>,
283    /// 传输速度(字节/秒)
284    pub transfer_speed: Option<f64>,
285    /// 重试次数
286    pub retry_count: u32,
287    /// 是否加密传输
288    pub encrypted: bool,
289    /// 是否分块传输
290    pub chunked: bool,
291    /// 分块数量
292    pub chunk_count: Option<usize>,
293    /// 校验和验证结果
294    pub checksum_verified: Option<bool>,
295    /// 错误信息(如果有)
296    pub error: Option<String>,
297    /// 警告信息
298    pub warnings: Vec<String>,
299    /// 自定义元数据
300    pub metadata: serde_json::Value,
301    /// 操作ID(用于去重和跟踪)
302    pub operation_id: String,
303    /// 源文件路径(可能不同)
304    pub source_path: Option<String>,
305    /// 目标文件路径
306    pub target_path: String,
307    /// 文件类型
308    pub file_type: FileType,
309    /// 文件权限
310    pub permissions: Option<u32>,
311    /// 文件修改时间
312    pub modified_time: Option<DateTime<Utc>>,
313}
314
315impl FileSyncResult {
316    pub fn new(path: String, operation: FileOperation) -> Self {
317        Self {
318            path: path.clone(),
319            status: FileSyncStatus::Pending,
320            size: 0,
321            transferred_size: 0,
322            source_hash: None,
323            target_hash: None,
324            operation,
325            start_time: None,
326            end_time: None,
327            duration_ms: None,
328            transfer_speed: None,
329            retry_count: 0,
330            encrypted: false,
331            chunked: false,
332            chunk_count: None,
333            checksum_verified: None,
334            error: None,
335            warnings: Vec::new(),
336            metadata: serde_json::Value::Null,
337            operation_id: Self::generate_operation_id(&path),
338            source_path: None,
339            target_path: path,
340            file_type: FileType::Unknown,
341            permissions: None,
342            modified_time: None,
343        }
344    }
345
346    pub fn from_diff(diff: &FileDiff, operation: FileOperation) -> Self {
347        let size = diff.source_info.as_ref().map(|f| f.size).unwrap_or(0);
348
349        let mut result = Self::new(diff.path.clone(), operation);
350        result.size = size;
351
352        if let Some(source_info) = &diff.source_info {
353            result.source_hash = source_info.file_hash.clone();
354            result.file_type = FileType::from_metadata(source_info);
355            result.modified_time = Some(
356                DateTime::from_timestamp(source_info.modified, 0).unwrap_or_else(|| Utc::now()),
357            );
358            result.permissions = Some(source_info.permissions);
359        }
360
361        if let Some(target_info) = &diff.target_info {
362            result.target_hash = target_info.file_hash.clone();
363        }
364
365        result.encrypted = diff.requires_encryption;
366        result.chunked = diff.requires_chunking;
367
368        result
369    }
370
371    fn generate_operation_id(path: &str) -> String {
372        use uuid::Uuid;
373        format!(
374            "op_{}_{}",
375            path.replace("/", "_").replace(".", "_"),
376            Uuid::new_v4().simple()
377        )
378    }
379
380    pub fn mark_started(&mut self) {
381        self.start_time = Some(Utc::now());
382        self.status = FileSyncStatus::Transferring;
383    }
384
385    pub fn mark_completed(&mut self, success: bool) {
386        self.end_time = Some(Utc::now());
387        self.status = if success {
388            FileSyncStatus::Success
389        } else {
390            FileSyncStatus::Failed
391        };
392
393        // 计算耗时
394        if let (Some(start), Some(end)) = (self.start_time, self.end_time) {
395            let duration = end - start;
396            self.duration_ms = Some(duration.num_milliseconds() as u64);
397
398            // 计算传输速度
399            if self.transferred_size > 0 && self.duration_ms.unwrap_or(0) > 0 {
400                let duration_secs = self.duration_ms.unwrap() as f64 / 1000.0;
401                self.transfer_speed = Some(self.transferred_size as f64 / duration_secs);
402            }
403        }
404    }
405
406    pub fn mark_retry(&mut self, error: Option<String>) {
407        self.status = FileSyncStatus::Retrying;
408        self.retry_count += 1;
409        self.error = error;
410        self.start_time = Some(Utc::now()); // 重置开始时间
411    }
412
413    pub fn mark_verifying(&mut self) {
414        self.status = FileSyncStatus::Verifying;
415    }
416
417    pub fn mark_verified(&mut self, verified: bool) {
418        self.checksum_verified = Some(verified);
419        self.status = if verified {
420            FileSyncStatus::Success
421        } else {
422            FileSyncStatus::Failed
423        };
424    }
425
426    pub fn add_warning(&mut self, warning: String) {
427        self.warnings.push(warning);
428    }
429
430    pub fn is_success(&self) -> bool {
431        self.status == FileSyncStatus::Success
432    }
433
434    pub fn is_failed(&self) -> bool {
435        self.status == FileSyncStatus::Failed
436    }
437
438    pub fn is_completed(&self) -> bool {
439        self.status.is_completed()
440    }
441
442    pub fn duration(&self) -> Option<std::time::Duration> {
443        self.duration_ms
444            .map(|ms| std::time::Duration::from_millis(ms))
445    }
446
447    pub fn human_readable_size(&self) -> String {
448        format_bytes(self.size)
449    }
450
451    pub fn human_readable_transferred(&self) -> String {
452        format_bytes(self.transferred_size)
453    }
454
455    pub fn human_readable_speed(&self) -> Option<String> {
456        self.transfer_speed.map(|speed| {
457            if speed >= 1024.0 * 1024.0 {
458                format!("{:.1} MB/s", speed / (1024.0 * 1024.0))
459            } else if speed >= 1024.0 {
460                format!("{:.1} KB/s", speed / 1024.0)
461            } else {
462                format!("{:.1} B/s", speed)
463            }
464        })
465    }
466
467    pub fn summary(&self) -> String {
468        let status_emoji = self.status.emoji();
469        let operation_str = self.operation.as_str();
470
471        let mut summary = format!("{} {}: {}", status_emoji, operation_str, self.path);
472
473        if let Some(duration) = self.duration() {
474            let secs = duration.as_secs_f64();
475            if secs > 0.0 {
476                summary.push_str(&format!(" ({:.2}s)", secs));
477            }
478        }
479
480        if let Some(speed) = self.human_readable_speed() {
481            summary.push_str(&format!(" @ {}", speed));
482        }
483
484        summary
485    }
486
487    pub fn detailed_info(&self) -> String {
488        let mut info = format!("文件: {}\n", self.path);
489        info.push_str(&format!(
490            "状态: {} {}\n",
491            self.status.emoji(),
492            self.status.as_str()
493        ));
494        info.push_str(&format!("操作: {}\n", self.operation.as_str()));
495        info.push_str(&format!("大小: {}\n", self.human_readable_size()));
496
497        if self.transferred_size > 0 {
498            info.push_str(&format!(
499                "传输大小: {}\n",
500                self.human_readable_transferred()
501            ));
502        }
503
504        if let (Some(start), Some(end)) = (self.start_time, self.end_time) {
505            info.push_str(&format!(
506                "开始时间: {}\n",
507                start.format("%Y-%m-%d %H:%M:%S")
508            ));
509            info.push_str(&format!("结束时间: {}\n", end.format("%Y-%m-%d %H:%M:%S")));
510        }
511
512        if let Some(duration) = self.duration() {
513            let secs = duration.as_secs_f64();
514            info.push_str(&format!("耗时: {:.2} 秒\n", secs));
515        }
516
517        if let Some(speed) = self.human_readable_speed() {
518            info.push_str(&format!("速度: {}\n", speed));
519        }
520
521        if self.encrypted {
522            info.push_str("加密: 是\n");
523        }
524
525        if self.chunked {
526            info.push_str(&format!(
527                "分块传输: 是 ({}块)\n",
528                self.chunk_count.unwrap_or(0)
529            ));
530        }
531
532        if let Some(verified) = self.checksum_verified {
533            info.push_str(&format!(
534                "校验和验证: {}\n",
535                if verified { "通过" } else { "失败" }
536            ));
537        }
538
539        if self.retry_count > 0 {
540            info.push_str(&format!("重试次数: {}\n", self.retry_count));
541        }
542
543        if let Some(error) = &self.error {
544            info.push_str(&format!("错误: {}\n", error));
545        }
546
547        if !self.warnings.is_empty() {
548            info.push_str(&format!("警告: {}\n", self.warnings.join("; ")));
549        }
550
551        info
552    }
553
554    pub fn to_json(&self) -> Result<String, serde_json::Error> {
555        serde_json::to_string_pretty(self)
556    }
557
558    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
559        serde_json::from_str(json)
560    }
561}
562
563/// 文件同步状态
564#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
565pub enum FileSyncStatus {
566    /// 等待中
567    Pending,
568    /// 准备中
569    Preparing,
570    /// 传输中
571    Transferring,
572    /// 验证中
573    Verifying,
574    /// 重试中
575    Retrying,
576    /// 成功
577    Success,
578    /// 失败
579    Failed,
580    /// 跳过
581    Skipped,
582    /// 冲突
583    Conflict,
584    /// 部分成功(如分块传输)
585    PartialSuccess,
586    /// 取消
587    Cancelled,
588    /// 等待用户确认
589    WaitingForUser,
590}
591
592impl FileSyncStatus {
593    pub fn as_str(&self) -> &'static str {
594        match self {
595            Self::Pending => "等待",
596            Self::Preparing => "准备",
597            Self::Transferring => "传输中",
598            Self::Verifying => "验证中",
599            Self::Retrying => "重试中",
600            Self::Success => "成功",
601            Self::Failed => "失败",
602            Self::Skipped => "跳过",
603            Self::Conflict => "冲突",
604            Self::PartialSuccess => "部分成功",
605            Self::Cancelled => "已取消",
606            Self::WaitingForUser => "等待用户",
607        }
608    }
609
610    pub fn emoji(&self) -> &'static str {
611        match self {
612            Self::Pending => "⏳",
613            Self::Preparing => "📝",
614            Self::Transferring => "📤",
615            Self::Verifying => "🔍",
616            Self::Retrying => "🔄",
617            Self::Success => "✅",
618            Self::Failed => "❌",
619            Self::Skipped => "⏭️",
620            Self::Conflict => "⚠️",
621            Self::PartialSuccess => "⚠️",
622            Self::Cancelled => "🚫",
623            Self::WaitingForUser => "🤔",
624        }
625    }
626
627    pub fn is_completed(&self) -> bool {
628        matches!(
629            self,
630            Self::Success | Self::Failed | Self::Skipped | Self::Cancelled | Self::Conflict
631        )
632    }
633
634    pub fn is_successful(&self) -> bool {
635        matches!(self, Self::Success | Self::PartialSuccess)
636    }
637
638    pub fn can_retry(&self) -> bool {
639        matches!(self, Self::Failed)
640    }
641}
642
643/// 文件操作类型
644#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
645pub enum FileOperation {
646    /// 上传文件
647    Upload,
648    /// 下载文件
649    Download,
650    /// 删除文件
651    Delete,
652    /// 移动文件
653    Move,
654    /// 复制文件
655    Copy,
656    /// 更新文件(修改内容)
657    Update,
658    /// 更新元数据
659    UpdateMetadata,
660    /// 验证文件
661    Verify,
662    /// 修复文件
663    Repair,
664    /// 加密文件
665    Encrypt,
666    /// 解密文件
667    Decrypt,
668    /// 压缩文件
669    Compress,
670    /// 解压文件
671    Decompress,
672    /// 创建目录
673    CreateDir,
674}
675
676impl FileOperation {
677    pub fn as_str(&self) -> &'static str {
678        match self {
679            Self::Upload => "上传",
680            Self::Download => "下载",
681            Self::Delete => "删除",
682            Self::Move => "移动",
683            Self::Copy => "复制",
684            Self::Update => "更新",
685            Self::UpdateMetadata => "更新元数据",
686            Self::Verify => "验证",
687            Self::Repair => "修复",
688            Self::Encrypt => "加密",
689            Self::Decrypt => "解密",
690            Self::Compress => "压缩",
691            Self::Decompress => "解压",
692            Self::CreateDir => "创建目录",
693        }
694    }
695
696    pub fn emoji(&self) -> &'static str {
697        match self {
698            Self::Upload => "📤",
699            Self::Download => "📥",
700            Self::Delete => "🗑️",
701            Self::Move => "📦",
702            Self::Copy => "📋",
703            Self::Update => "🔄",
704            Self::UpdateMetadata => "📊",
705            Self::Verify => "🔍",
706            Self::Repair => "🔧",
707            Self::Encrypt => "🔒",
708            Self::Decrypt => "🔓",
709            Self::Compress => "🗜️",
710            Self::Decompress => "🗜️",
711            Self::CreateDir => "📁",
712        }
713    }
714
715    pub fn from_diff_action(action: DiffAction) -> Self {
716        match action {
717            DiffAction::Upload => FileOperation::Upload,
718            DiffAction::Download => FileOperation::Download,
719            DiffAction::Delete => FileOperation::Delete,
720            DiffAction::Move => FileOperation::Move,
721            DiffAction::Update => FileOperation::Update,
722            DiffAction::Conflict => FileOperation::Verify, // 冲突文件需要验证
723            DiffAction::Unchanged => FileOperation::Verify,
724            DiffAction::CreateDir => FileOperation::CreateDir,
725        }
726    }
727}
728
729/// 文件类型枚举
730#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
731pub enum FileType {
732    /// 未知类型
733    Unknown,
734    /// 文本文件
735    Text,
736    /// 图片文件
737    Image,
738    /// 视频文件
739    Video,
740    /// 音频文件
741    Audio,
742    /// 文档文件
743    Document,
744    /// 压缩文件
745    Archive,
746    /// 可执行文件
747    Executable,
748    /// 配置文件
749    Config,
750    /// 日志文件
751    Log,
752    /// 数据库文件
753    Database,
754    /// 代码文件
755    Code,
756    /// 字体文件
757    Font,
758    /// 3D模型文件
759    Model3D,
760    /// 电子表格
761    Spreadsheet,
762    /// 演示文稿
763    Presentation,
764    /// PDF文档
765    Pdf,
766    /// 目录
767    Directory,
768    /// 符号链接
769    Symlink,
770    /// 设备文件
771    Device,
772}
773
774impl FileType {
775    pub fn from_extension(extension: &str) -> Self {
776        let ext = extension.to_lowercase();
777
778        match ext.as_str() {
779            "txt" | "md" | "markdown" | "rst" | "log" => FileType::Text,
780            "jpg" | "jpeg" | "png" | "gif" | "bmp" | "svg" | "webp" | "ico" => FileType::Image,
781            "mp4" | "avi" | "mkv" | "mov" | "wmv" | "flv" | "webm" => FileType::Video,
782            "mp3" | "wav" | "flac" | "aac" | "ogg" | "m4a" => FileType::Audio,
783            "pdf" => FileType::Pdf,
784            "doc" | "docx" | "odt" | "rtf" => FileType::Document,
785            "xls" | "xlsx" | "ods" | "csv" => FileType::Spreadsheet,
786            "ppt" | "pptx" | "odp" => FileType::Presentation,
787            "zip" | "rar" | "7z" | "tar" | "gz" | "bz2" => FileType::Archive,
788            "exe" | "bin" | "app" | "sh" | "bat" | "cmd" => FileType::Executable,
789            "json" | "yaml" | "yml" | "toml" | "ini" | "cfg" | "conf" => FileType::Config,
790            "db" | "sqlite" | "mdb" | "accdb" => FileType::Database,
791            "rs" | "py" | "js" | "ts" | "java" | "cpp" | "c" | "h" | "go" | "php" | "rb" => {
792                FileType::Code
793            }
794            "ttf" | "otf" | "woff" | "woff2" => FileType::Font,
795            "stl" | "obj" | "fbx" | "blend" => FileType::Model3D,
796            _ => FileType::Unknown,
797        }
798    }
799
800    pub fn from_path(path: &Path) -> Self {
801        if path.is_dir() {
802            return FileType::Directory;
803        }
804
805        if let Some(extension) = path.extension() {
806            Self::from_extension(extension.to_string_lossy().as_ref())
807        } else {
808            FileType::Unknown
809        }
810    }
811
812    pub fn from_metadata(metadata: &crate::sync::diff::FileMetadata) -> Self {
813        if metadata.is_dir {
814            FileType::Directory
815        } else if metadata.is_symlink {
816            FileType::Symlink
817        } else if let Some(mime_type) = &metadata.mime_type {
818            Self::from_mime_type(mime_type)
819        } else if let Some(extension) = metadata.path.extension() {
820            Self::from_extension(extension.to_string_lossy().as_ref())
821        } else {
822            FileType::Unknown
823        }
824    }
825
826    pub fn from_mime_type(mime_type: &str) -> Self {
827        match mime_type {
828            t if t.starts_with("text/") => FileType::Text,
829            t if t.starts_with("image/") => FileType::Image,
830            t if t.starts_with("video/") => FileType::Video,
831            t if t.starts_with("audio/") => FileType::Audio,
832            t if t.starts_with("application/pdf") => FileType::Pdf,
833            t if t.contains("document") => FileType::Document,
834            t if t.contains("spreadsheet") => FileType::Spreadsheet,
835            t if t.contains("presentation") => FileType::Presentation,
836            t if t.contains("zip") || t.contains("compressed") => FileType::Archive,
837            t if t.contains("executable") || t.contains("octet-stream") => FileType::Executable,
838            t if t.contains("json") || t.contains("yaml") || t.contains("xml") => FileType::Config,
839            t if t.contains("database") => FileType::Database,
840            t if t.contains("font") => FileType::Font,
841            _ => FileType::Unknown,
842        }
843    }
844
845    pub fn as_str(&self) -> &'static str {
846        match self {
847            Self::Unknown => "未知",
848            Self::Text => "文本",
849            Self::Image => "图片",
850            Self::Video => "视频",
851            Self::Audio => "音频",
852            Self::Document => "文档",
853            Self::Archive => "压缩包",
854            Self::Executable => "可执行文件",
855            Self::Config => "配置文件",
856            Self::Log => "日志",
857            Self::Database => "数据库",
858            Self::Code => "代码",
859            Self::Font => "字体",
860            Self::Model3D => "3D模型",
861            Self::Spreadsheet => "电子表格",
862            Self::Presentation => "演示文稿",
863            Self::Pdf => "PDF",
864            Self::Directory => "目录",
865            Self::Symlink => "符号链接",
866            Self::Device => "设备文件",
867        }
868    }
869
870    pub fn emoji(&self) -> &'static str {
871        match self {
872            Self::Unknown => "📄",
873            Self::Text => "📄",
874            Self::Image => "🖼️",
875            Self::Video => "🎬",
876            Self::Audio => "🎵",
877            Self::Document => "📄",
878            Self::Archive => "🗜️",
879            Self::Executable => "⚙️",
880            Self::Config => "⚙️",
881            Self::Log => "📋",
882            Self::Database => "🗄️",
883            Self::Code => "💻",
884            Self::Font => "🔤",
885            Self::Model3D => "🧊",
886            Self::Spreadsheet => "📊",
887            Self::Presentation => "📽️",
888            Self::Pdf => "📘",
889            Self::Directory => "📁",
890            Self::Symlink => "🔗",
891            Self::Device => "💾",
892        }
893    }
894
895    pub fn is_compressible(&self) -> bool {
896        matches!(
897            self,
898            Self::Text
899                | Self::Code
900                | Self::Document
901                | Self::Spreadsheet
902                | Self::Presentation
903                | Self::Config
904                | Self::Log
905        )
906    }
907
908    pub fn is_binary(&self) -> bool {
909        !matches!(
910            self,
911            Self::Text
912                | Self::Code
913                | Self::Config
914                | Self::Document
915                | Self::Spreadsheet
916                | Self::Presentation
917                | Self::Log
918        )
919    }
920}
921
922/// 同步统计信息
923#[derive(Debug, Clone, Serialize, Deserialize, Default)]
924pub struct SyncStatistics {
925    /// 总文件数
926    pub total_files: usize,
927    /// 成功同步的文件数
928    pub files_synced: usize,
929    /// 跳过的文件数
930    pub files_skipped: usize,
931    /// 失败的文件数
932    pub files_failed: usize,
933    /// 冲突的文件数
934    pub conflicts: usize,
935    /// 总字节数
936    pub total_bytes: u64,
937    /// 已传输的字节数
938    pub transferred_bytes: u64,
939    /// 平均传输速率(字节/秒)
940    pub average_speed: f64,
941    /// 最大传输速率(字节/秒)
942    pub max_speed: f64,
943    /// 最小传输速率(字节/秒)
944    pub min_speed: f64,
945    /// 总耗时(秒)
946    pub duration_seconds: f64,
947    /// 重试总次数
948    pub total_retries: u32,
949    /// 加密文件数
950    pub encrypted_files: usize,
951    /// 分块传输的文件数
952    pub chunked_files: usize,
953    /// 校验和验证的文件数
954    pub verified_files: usize,
955    /// 校验和失败的文件数
956    pub verification_failed: usize,
957    /// 按文件类型统计
958    pub file_type_stats: HashMap<FileType, FileTypeStats>,
959    /// 按操作类型统计
960    pub operation_stats: HashMap<FileOperation, usize>,
961    // 传输速率
962    pub transfer_rate: f64,
963}
964
965impl SyncStatistics {
966    pub fn new() -> Self {
967        Self {
968            total_files: 0,
969            files_synced: 0,
970            files_skipped: 0,
971            files_failed: 0,
972            conflicts: 0,
973            total_bytes: 0,
974            transferred_bytes: 0,
975            average_speed: 0.0,
976            max_speed: 0.0,
977            min_speed: f64::MAX,
978            duration_seconds: 0.0,
979            total_retries: 0,
980            encrypted_files: 0,
981            chunked_files: 0,
982            verified_files: 0,
983            verification_failed: 0,
984            file_type_stats: HashMap::new(),
985            operation_stats: HashMap::new(),
986            transfer_rate: 0.0,
987        }
988    }
989
990    pub fn add_file_result(&mut self, result: &FileSyncResult) {
991        self.total_files += 1;
992
993        match result.status {
994            FileSyncStatus::Success | FileSyncStatus::PartialSuccess => {
995                self.files_synced += 1;
996            }
997            FileSyncStatus::Failed => {
998                self.files_failed += 1;
999            }
1000            FileSyncStatus::Skipped => {
1001                self.files_skipped += 1;
1002            }
1003            FileSyncStatus::Conflict => {
1004                self.conflicts += 1;
1005            }
1006            _ => {}
1007        }
1008
1009        self.total_bytes += result.size;
1010        self.transferred_bytes += result.transferred_size;
1011        self.total_retries += result.retry_count;
1012
1013        if result.encrypted {
1014            self.encrypted_files += 1;
1015        }
1016
1017        if result.chunked {
1018            self.chunked_files += 1;
1019        }
1020
1021        if let Some(verified) = result.checksum_verified {
1022            if verified {
1023                self.verified_files += 1;
1024            } else {
1025                self.verification_failed += 1;
1026            }
1027        }
1028
1029        // 更新文件类型统计
1030        let stats = self
1031            .file_type_stats
1032            .entry(result.file_type)
1033            .or_insert_with(FileTypeStats::new);
1034        stats.add_file(result);
1035
1036        // 更新操作类型统计
1037        *self.operation_stats.entry(result.operation).or_insert(0) += 1;
1038    }
1039
1040    pub fn update_speed_metrics(&mut self, speed: f64) {
1041        self.average_speed = (self.average_speed + speed) / 2.0;
1042        self.max_speed = self.max_speed.max(speed);
1043        self.min_speed = self.min_speed.min(speed);
1044    }
1045
1046    pub fn finalize(&mut self, duration: f64) {
1047        self.duration_seconds = duration;
1048
1049        if self.duration_seconds > 0.0 {
1050            self.average_speed = self.transferred_bytes as f64 / self.duration_seconds;
1051        }
1052
1053        if self.min_speed == f64::MAX {
1054            self.min_speed = 0.0;
1055        }
1056    }
1057
1058    pub fn success_rate(&self) -> f64 {
1059        if self.total_files == 0 {
1060            return 0.0;
1061        }
1062        (self.files_synced as f64 / self.total_files as f64) * 100.0
1063    }
1064
1065    pub fn failure_rate(&self) -> f64 {
1066        if self.total_files == 0 {
1067            return 0.0;
1068        }
1069        (self.files_failed as f64 / self.total_files as f64) * 100.0
1070    }
1071
1072    pub fn skip_rate(&self) -> f64 {
1073        if self.total_files == 0 {
1074            return 0.0;
1075        }
1076        (self.files_skipped as f64 / self.total_files as f64) * 100.0
1077    }
1078
1079    pub fn verification_success_rate(&self) -> f64 {
1080        let total_verified = self.verified_files + self.verification_failed;
1081        if total_verified == 0 {
1082            return 0.0;
1083        }
1084        (self.verified_files as f64 / total_verified as f64) * 100.0
1085    }
1086
1087    pub fn average_file_size(&self) -> f64 {
1088        if self.total_files == 0 {
1089            0.0
1090        } else {
1091            self.total_bytes as f64 / self.total_files as f64
1092        }
1093    }
1094
1095    pub fn human_readable_total_bytes(&self) -> String {
1096        format_bytes(self.total_bytes)
1097    }
1098
1099    pub fn human_readable_transferred_bytes(&self) -> String {
1100        format_bytes(self.transferred_bytes)
1101    }
1102
1103    pub fn human_readable_average_speed(&self) -> String {
1104        if self.average_speed >= 1024.0 * 1024.0 {
1105            format!("{:.1} MB/s", self.average_speed / (1024.0 * 1024.0))
1106        } else if self.average_speed >= 1024.0 {
1107            format!("{:.1} KB/s", self.average_speed / 1024.0)
1108        } else {
1109            format!("{:.1} B/s", self.average_speed)
1110        }
1111    }
1112
1113    pub fn summary(&self) -> String {
1114        let success_rate = self.success_rate();
1115        let verification_rate = self.verification_success_rate();
1116
1117        format!(
1118            "文件: {}/{} ({:.1}% 成功率), 数据: {}/{}, 速度: {}, 耗时: {:.1}s",
1119            self.files_synced,
1120            self.total_files,
1121            success_rate,
1122            self.human_readable_transferred_bytes(),
1123            self.human_readable_total_bytes(),
1124            self.human_readable_average_speed(),
1125            self.duration_seconds
1126        )
1127    }
1128
1129    pub fn detailed_report(&self) -> String {
1130        let mut report = String::new();
1131
1132        report.push_str(&format!("📊 同步统计详情\n"));
1133        report.push_str(&format!("文件总数: {}\n", self.total_files));
1134        report.push_str(&format!(
1135            "成功同步: {} ({:.1}%)\n",
1136            self.files_synced,
1137            self.success_rate()
1138        ));
1139        report.push_str(&format!(
1140            "同步失败: {} ({:.1}%)\n",
1141            self.files_failed,
1142            self.failure_rate()
1143        ));
1144        report.push_str(&format!(
1145            "跳过文件: {} ({:.1}%)\n",
1146            self.files_skipped,
1147            self.skip_rate()
1148        ));
1149        report.push_str(&format!("冲突文件: {}\n", self.conflicts));
1150        report.push_str(&format!("重试次数: {}\n", self.total_retries));
1151        report.push_str(&format!(
1152            "总数据量: {}\n",
1153            self.human_readable_total_bytes()
1154        ));
1155        report.push_str(&format!(
1156            "传输数据: {}\n",
1157            self.human_readable_transferred_bytes()
1158        ));
1159        report.push_str(&format!(
1160            "平均文件大小: {:.1} KB\n",
1161            self.average_file_size() / 1024.0
1162        ));
1163        report.push_str(&format!(
1164            "平均速度: {}\n",
1165            self.human_readable_average_speed()
1166        ));
1167        report.push_str(&format!(
1168            "最大速度: {:.1} MB/s\n",
1169            self.max_speed / (1024.0 * 1024.0)
1170        ));
1171        report.push_str(&format!("最小速度: {:.1} KB/s\n", self.min_speed / 1024.0));
1172        report.push_str(&format!("总耗时: {:.1} 秒\n", self.duration_seconds));
1173        report.push_str(&format!("加密文件: {}\n", self.encrypted_files));
1174        report.push_str(&format!("分块传输: {}\n", self.chunked_files));
1175        report.push_str(&format!(
1176            "校验和验证: {}/{} ({:.1}% 成功率)\n",
1177            self.verified_files,
1178            self.verified_files + self.verification_failed,
1179            self.verification_success_rate()
1180        ));
1181
1182        report.push_str("\n📁 文件类型统计:\n");
1183        let mut file_types: Vec<_> = self.file_type_stats.iter().collect();
1184        file_types.sort_by_key(|(_, stats)| std::cmp::Reverse(stats.count));
1185
1186        for (file_type, stats) in file_types.iter().take(10) {
1187            let percentage = if self.total_files > 0 {
1188                (stats.count as f64 / self.total_files as f64) * 100.0
1189            } else {
1190                0.0
1191            };
1192            report.push_str(&format!(
1193                "  {} {}: {} ({:.1}%) - {}\n",
1194                file_type.emoji(),
1195                file_type.as_str(),
1196                stats.count,
1197                percentage,
1198                format_bytes(stats.total_size)
1199            ));
1200        }
1201
1202        report.push_str("\n🔄 操作类型统计:\n");
1203        let mut operations: Vec<_> = self.operation_stats.iter().collect();
1204        operations.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
1205
1206        for (operation, count) in operations {
1207            let percentage = if self.total_files > 0 {
1208                (*count as f64 / self.total_files as f64) * 100.0
1209            } else {
1210                0.0
1211            };
1212            report.push_str(&format!(
1213                "  {} {}: {} ({:.1}%)\n",
1214                operation.emoji(),
1215                operation.as_str(),
1216                count,
1217                percentage
1218            ));
1219        }
1220
1221        report
1222    }
1223}
1224
1225/// 文件类型统计
1226#[derive(Debug, Clone, Serialize, Deserialize)]
1227pub struct FileTypeStats {
1228    /// 文件数量
1229    pub count: usize,
1230    /// 总大小
1231    pub total_size: u64,
1232    /// 成功数量
1233    pub success_count: usize,
1234    /// 失败数量
1235    pub failure_count: usize,
1236    /// 平均传输速度
1237    pub average_speed: f64,
1238    /// 总传输时间(秒)
1239    pub total_duration: f64,
1240    /// 加密文件数量
1241    pub encrypted_count: usize,
1242}
1243
1244impl FileTypeStats {
1245    pub fn new() -> Self {
1246        Self {
1247            count: 0,
1248            total_size: 0,
1249            success_count: 0,
1250            failure_count: 0,
1251            average_speed: 0.0,
1252            total_duration: 0.0,
1253            encrypted_count: 0,
1254        }
1255    }
1256
1257    pub fn add_file(&mut self, result: &FileSyncResult) {
1258        self.count += 1;
1259        self.total_size += result.size;
1260
1261        if result.is_success() {
1262            self.success_count += 1;
1263        } else if result.is_failed() {
1264            self.failure_count += 1;
1265        }
1266
1267        if let Some(duration) = result.duration() {
1268            self.total_duration += duration.as_secs_f64();
1269
1270            if duration.as_secs_f64() > 0.0 && result.transferred_size > 0 {
1271                let speed = result.transferred_size as f64 / duration.as_secs_f64();
1272                self.average_speed = (self.average_speed + speed) / 2.0;
1273            }
1274        }
1275
1276        if result.encrypted {
1277            self.encrypted_count += 1;
1278        }
1279    }
1280
1281    pub fn success_rate(&self) -> f64 {
1282        if self.count == 0 {
1283            return 0.0;
1284        }
1285        (self.success_count as f64 / self.count as f64) * 100.0
1286    }
1287}