1use async_trait::async_trait;
3use std::fmt;
4
5#[async_trait]
7pub trait Task: Send + Sync {
8 async fn execute(&self) -> TaskResult;
9 fn task_type(&self) -> &'static str;
10 fn task_id(&self) -> String;
11 fn estimated_duration(&self) -> Option<std::time::Duration> {
12 None
13 }
14 fn description(&self) -> String {
15 format!("{} 任務", self.task_type())
16 }
17}
18
19#[derive(Debug, Clone)]
21pub enum TaskResult {
22 Success(String),
23 Failed(String),
24 Cancelled,
25 PartialSuccess(String, String),
26}
27
28#[derive(Debug, Clone)]
30pub enum TaskStatus {
31 Pending,
32 Running,
33 Completed(TaskResult),
34 Failed(String),
35 Cancelled,
36}
37
38impl fmt::Display for TaskResult {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 TaskResult::Success(msg) => write!(f, "✓ {}", msg),
42 TaskResult::Failed(msg) => write!(f, "✗ {}", msg),
43 TaskResult::Cancelled => write!(f, "⚠ 任務被取消"),
44 TaskResult::PartialSuccess(success, warn) => {
45 write!(f, "⚠ {} (警告: {})", success, warn)
46 }
47 }
48 }
49}
50
51impl fmt::Display for TaskStatus {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 TaskStatus::Pending => write!(f, "等待中"),
55 TaskStatus::Running => write!(f, "執行中"),
56 TaskStatus::Completed(result) => write!(f, "已完成: {}", result),
57 TaskStatus::Failed(msg) => write!(f, "失敗: {}", msg),
58 TaskStatus::Cancelled => write!(f, "已取消"),
59 }
60 }
61}
62
63pub struct FileProcessingTask {
65 pub input_path: std::path::PathBuf,
66 pub output_path: Option<std::path::PathBuf>,
67 pub operation: ProcessingOperation,
68}
69
70#[derive(Debug, Clone)]
72pub enum ProcessingOperation {
73 ConvertFormat { from: String, to: String },
74 SyncSubtitle { audio_path: std::path::PathBuf },
75 MatchFiles { recursive: bool },
76 ValidateFormat,
77}
78
79#[async_trait]
80impl Task for FileProcessingTask {
81 async fn execute(&self) -> TaskResult {
82 match &self.operation {
83 ProcessingOperation::ConvertFormat { from, to } => {
84 match self.convert_format(from, to).await {
85 Ok(path) => TaskResult::Success(format!(
86 "成功轉換 {} -> {}: {}",
87 from,
88 to,
89 path.display()
90 )),
91 Err(e) => {
92 TaskResult::Failed(format!("轉換失敗 {}: {}", self.input_path.display(), e))
93 }
94 }
95 }
96 ProcessingOperation::SyncSubtitle { .. } => {
97 TaskResult::Failed("同步功能未實作".to_string())
99 }
100 ProcessingOperation::MatchFiles { recursive } => {
101 match self.match_files(*recursive).await {
102 Ok(m) => TaskResult::Success(format!("檔案匹配完成: 找到 {} 組匹配", m.len())),
103 Err(e) => TaskResult::Failed(format!("匹配失敗: {}", e)),
104 }
105 }
106 ProcessingOperation::ValidateFormat => match self.validate_format().await {
107 Ok(true) => {
108 TaskResult::Success(format!("格式驗證通過: {}", self.input_path.display()))
109 }
110 Ok(false) => {
111 TaskResult::Failed(format!("格式驗證失敗: {}", self.input_path.display()))
112 }
113 Err(e) => TaskResult::Failed(format!("驗證錯誤: {}", e)),
114 },
115 }
116 }
117
118 fn task_type(&self) -> &'static str {
119 match &self.operation {
120 ProcessingOperation::ConvertFormat { .. } => "convert",
121 ProcessingOperation::SyncSubtitle { .. } => "sync",
122 ProcessingOperation::MatchFiles { .. } => "match",
123 ProcessingOperation::ValidateFormat => "validate",
124 }
125 }
126
127 fn task_id(&self) -> String {
128 use std::collections::hash_map::DefaultHasher;
129 use std::hash::{Hash, Hasher};
130 let mut hasher = DefaultHasher::new();
131 self.input_path.hash(&mut hasher);
132 self.operation.hash(&mut hasher);
133 format!("{}_{:x}", self.task_type(), hasher.finish())
134 }
135
136 fn estimated_duration(&self) -> Option<std::time::Duration> {
137 if let Ok(meta) = std::fs::metadata(&self.input_path) {
138 let size_mb = meta.len() as f64 / 1_048_576.0;
139 let secs = match &self.operation {
140 ProcessingOperation::ConvertFormat { .. } => size_mb * 0.1,
141 ProcessingOperation::SyncSubtitle { .. } => size_mb * 0.5,
142 ProcessingOperation::MatchFiles { .. } => 2.0,
143 ProcessingOperation::ValidateFormat => size_mb * 0.05,
144 };
145 Some(std::time::Duration::from_secs_f64(secs))
146 } else {
147 None
148 }
149 }
150
151 fn description(&self) -> String {
152 match &self.operation {
153 ProcessingOperation::ConvertFormat { from, to } => {
154 format!("轉換 {} 從 {} 到 {}", self.input_path.display(), from, to)
155 }
156 ProcessingOperation::SyncSubtitle { audio_path } => format!(
157 "同步字幕 {} 與音訊 {}",
158 self.input_path.display(),
159 audio_path.display()
160 ),
161 ProcessingOperation::MatchFiles { recursive } => format!(
162 "匹配 {} 中的檔案{}",
163 self.input_path.display(),
164 if *recursive { " (遞歸)" } else { "" }
165 ),
166 ProcessingOperation::ValidateFormat => {
167 format!("驗證 {} 的格式", self.input_path.display())
168 }
169 }
170 }
171}
172
173impl FileProcessingTask {
174 async fn convert_format(&self, _from: &str, _to: &str) -> crate::Result<std::path::PathBuf> {
175 Ok(self.input_path.clone())
177 }
178
179 async fn sync_subtitle(
180 &self,
181 _audio_path: &std::path::Path,
182 ) -> crate::Result<crate::core::sync::SyncResult> {
183 Err(crate::error::SubXError::parallel_processing(
185 "sync_subtitle not implemented".to_string(),
186 ))
187 }
188
189 async fn match_files(&self, _recursive: bool) -> crate::Result<Vec<()>> {
190 Ok(Vec::new())
192 }
193
194 async fn validate_format(&self) -> crate::Result<bool> {
195 Ok(true)
197 }
198}
199
200impl std::hash::Hash for ProcessingOperation {
202 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
203 match self {
204 ProcessingOperation::ConvertFormat { from, to } => {
205 "convert".hash(state);
206 from.hash(state);
207 to.hash(state);
208 }
209 ProcessingOperation::SyncSubtitle { audio_path } => {
210 "sync".hash(state);
211 audio_path.hash(state);
212 }
213 ProcessingOperation::MatchFiles { recursive } => {
214 "match".hash(state);
215 recursive.hash(state);
216 }
217 ProcessingOperation::ValidateFormat => {
218 "validate".hash(state);
219 }
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use tempfile::TempDir;
228
229 #[tokio::test]
230 async fn test_file_processing_task_validate_format() {
231 let tmp = TempDir::new().unwrap();
232 let test_file = tmp.path().join("test.srt");
233 tokio::fs::write(&test_file, "1\n00:00:01,000 --> 00:00:02,000\nTest\n")
234 .await
235 .unwrap();
236 let task = FileProcessingTask {
237 input_path: test_file.clone(),
238 output_path: None,
239 operation: ProcessingOperation::ValidateFormat,
240 };
241 let result = task.execute().await;
242 assert!(matches!(result, TaskResult::Success(_)));
243 }
244}