subx_cli/core/parallel/
task.rs

1//! Task definition and utilities for parallel processing
2use async_trait::async_trait;
3use std::fmt;
4
5/// Trait defining a unit of work
6#[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/// Result of task execution
20#[derive(Debug, Clone)]
21pub enum TaskResult {
22    Success(String),
23    Failed(String),
24    Cancelled,
25    PartialSuccess(String, String),
26}
27
28/// Status of a task
29#[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
63/// Task for processing files (convert, sync, match, validate)
64pub struct FileProcessingTask {
65    pub input_path: std::path::PathBuf,
66    pub output_path: Option<std::path::PathBuf>,
67    pub operation: ProcessingOperation,
68}
69
70/// Supported operations for file processing
71#[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                // Sync not supported in parallel tasks
98                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        // Stub convert: simply return input path
176        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        // Stub implementation: sync not available
184        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        // Stub implementation: no actual matching
191        Ok(Vec::new())
192    }
193
194    async fn validate_format(&self) -> crate::Result<bool> {
195        // Stub validate: always succeed
196        Ok(true)
197    }
198}
199
200// impl Hash for ProcessingOperation to support task_id generation
201impl 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}