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 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#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
183pub enum SyncStatus {
184 Pending,
186 Running,
188 Success,
190 PartialSuccess,
192 Failed,
194 Cancelled,
196 Retrying,
198 Paused,
200 Verifying,
202 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#[derive(Debug, Serialize, Deserialize)]
262pub struct FileSyncResult {
263 pub path: String,
265 pub status: FileSyncStatus,
267 pub size: u64,
269 pub transferred_size: u64,
271 pub source_hash: Option<String>,
273 pub target_hash: Option<String>,
275 pub operation: FileOperation,
277 pub start_time: Option<DateTime<Utc>>,
279 pub end_time: Option<DateTime<Utc>>,
281 pub duration_ms: Option<u64>,
283 pub transfer_speed: Option<f64>,
285 pub retry_count: u32,
287 pub encrypted: bool,
289 pub chunked: bool,
291 pub chunk_count: Option<usize>,
293 pub checksum_verified: Option<bool>,
295 pub error: Option<String>,
297 pub warnings: Vec<String>,
299 pub metadata: serde_json::Value,
301 pub operation_id: String,
303 pub source_path: Option<String>,
305 pub target_path: String,
307 pub file_type: FileType,
309 pub permissions: Option<u32>,
311 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 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 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()); }
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#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
565pub enum FileSyncStatus {
566 Pending,
568 Preparing,
570 Transferring,
572 Verifying,
574 Retrying,
576 Success,
578 Failed,
580 Skipped,
582 Conflict,
584 PartialSuccess,
586 Cancelled,
588 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#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
645pub enum FileOperation {
646 Upload,
648 Download,
650 Delete,
652 Move,
654 Copy,
656 Update,
658 UpdateMetadata,
660 Verify,
662 Repair,
664 Encrypt,
666 Decrypt,
668 Compress,
670 Decompress,
672 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, DiffAction::Unchanged => FileOperation::Verify,
724 DiffAction::CreateDir => FileOperation::CreateDir,
725 }
726 }
727}
728
729#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
731pub enum FileType {
732 Unknown,
734 Text,
736 Image,
738 Video,
740 Audio,
742 Document,
744 Archive,
746 Executable,
748 Config,
750 Log,
752 Database,
754 Code,
756 Font,
758 Model3D,
760 Spreadsheet,
762 Presentation,
764 Pdf,
766 Directory,
768 Symlink,
770 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
924pub struct SyncStatistics {
925 pub total_files: usize,
927 pub files_synced: usize,
929 pub files_skipped: usize,
931 pub files_failed: usize,
933 pub conflicts: usize,
935 pub total_bytes: u64,
937 pub transferred_bytes: u64,
939 pub average_speed: f64,
941 pub max_speed: f64,
943 pub min_speed: f64,
945 pub duration_seconds: f64,
947 pub total_retries: u32,
949 pub encrypted_files: usize,
951 pub chunked_files: usize,
953 pub verified_files: usize,
955 pub verification_failed: usize,
957 pub file_type_stats: HashMap<FileType, FileTypeStats>,
959 pub operation_stats: HashMap<FileOperation, usize>,
961 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 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 *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#[derive(Debug, Clone, Serialize, Deserialize)]
1227pub struct FileTypeStats {
1228 pub count: usize,
1230 pub total_size: u64,
1232 pub success_count: usize,
1234 pub failure_count: usize,
1236 pub average_speed: f64,
1238 pub total_duration: f64,
1240 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}