Skip to main content

client_core/patch_executor/
mod.rs

1// client-core/src/patch_executor/mod.rs
2//! 增量升级补丁执行器模块
3//!
4//! 本模块负责处理增量升级的核心逻辑,包括:
5//! - 文件操作执行器:安全的文件替换、删除和回滚
6//! - 补丁包处理器:下载、验证和解压补丁包
7//! - 主补丁执行器:协调整个补丁应用流程
8
9pub mod error;
10pub mod file_operations;
11pub mod patch_processor;
12
13// 重新导出主要接口
14pub use error::PatchExecutorError;
15pub use file_operations::FileOperationExecutor;
16pub use patch_processor::PatchProcessor;
17
18use crate::api_types::{PatchOperations, PatchPackageInfo};
19use std::path::{Path, PathBuf};
20use tracing::{debug, error, info, warn};
21
22/// 主补丁执行器
23///
24/// 负责协调整个补丁应用流程,包括下载、验证、解压和应用补丁
25pub struct PatchExecutor {
26    /// 工作目录
27    work_dir: PathBuf,
28    /// 文件操作执行器
29    file_executor: FileOperationExecutor,
30    /// 补丁处理器
31    patch_processor: PatchProcessor,
32    /// 是否启用了备份
33    backup_enabled: bool,
34}
35
36impl PatchExecutor {
37    /// 创建新的补丁执行器
38    pub fn new(work_dir: PathBuf) -> Result<Self, PatchExecutorError> {
39        let file_executor = FileOperationExecutor::new(work_dir.clone())?;
40        let patch_processor = PatchProcessor::new()?;
41
42        Ok(Self {
43            work_dir,
44            file_executor,
45            patch_processor,
46            backup_enabled: false,
47        })
48    }
49
50    /// 启用备份模式(支持回滚)
51    pub fn enable_backup(&mut self) -> Result<(), PatchExecutorError> {
52        self.file_executor.enable_backup()?;
53        self.backup_enabled = true;
54        info!("Patch execution backup mode enabled");
55        Ok(())
56    }
57
58    /// 应用补丁包
59    ///
60    /// # 参数
61    /// * `patch_info` - 补丁包信息
62    /// * `operations` - 补丁操作定义
63    /// * `progress_callback` - 进度回调函数
64    pub async fn apply_patch<F>(
65        &mut self,
66        patch_info: &PatchPackageInfo,
67        operations: &PatchOperations,
68        progress_callback: F,
69    ) -> Result<(), PatchExecutorError>
70    where
71        F: Fn(f64) + Send + Sync,
72    {
73        info!("Starting to apply incremental patch...");
74        progress_callback(0.0);
75
76        // 验证前置条件
77        self.validate_preconditions(operations)?;
78        progress_callback(0.05);
79
80        // 执行补丁应用流程
81        match self
82            .execute_patch_pipeline(patch_info, operations, &progress_callback)
83            .await
84        {
85            Ok(_) => {
86                progress_callback(1.0);
87                info!("Incremental patch applied successfully");
88                Ok(())
89            }
90            Err(e) => {
91                error!("Patch application failed: {}", e);
92
93                // 根据错误类型决定是否回滚
94                if e.requires_rollback() && self.backup_enabled {
95                    warn!("Starting automatic rollback...");
96                    if let Err(rollback_err) = self.rollback().await {
97                        error!("Rollback failed: {}", rollback_err);
98                        return Err(PatchExecutorError::rollback_failed(format!(
99                            "Original error: {e}, rollback error: {rollback_err}"
100                        )));
101                    }
102                    info!("Automatic rollback completed");
103                }
104
105                Err(e)
106            }
107        }
108    }
109
110    /// 验证前置条件
111    fn validate_preconditions(
112        &self,
113        operations: &PatchOperations,
114    ) -> Result<(), PatchExecutorError> {
115        debug!("Validating patch application preconditions");
116
117        // 验证工作目录存在且可写
118        if !self.work_dir.exists() {
119            return Err(PatchExecutorError::path_error(format!(
120                "Working directory does not exist: {:?}",
121                self.work_dir
122            )));
123        }
124
125        // 验证操作不为空
126        let total_operations = operations.total_operations();
127
128        if total_operations == 0 {
129            return Err(PatchExecutorError::custom("Patch operations are empty"));
130        }
131
132        debug!(
133            "Preconditions validated, total {} operations",
134            total_operations
135        );
136        Ok(())
137    }
138
139    /// 执行补丁应用管道
140    async fn execute_patch_pipeline<F>(
141        &mut self,
142        patch_info: &PatchPackageInfo,
143        operations: &PatchOperations,
144        progress_callback: &F,
145    ) -> Result<(), PatchExecutorError>
146    where
147        F: Fn(f64) + Send + Sync,
148    {
149        // 1. 下载并验证补丁包
150        info!("Downloading patch package...");
151        let patch_path = self.patch_processor.download_patch(patch_info).await?;
152        progress_callback(0.25);
153
154        // 2. 验证补丁完整性和签名
155        info!("Verifying patch integrity...");
156        self.patch_processor
157            .verify_patch_integrity(&patch_path, patch_info)
158            .await?;
159        progress_callback(0.35);
160
161        // 3. 解压补丁包
162        info!("Extracting patch package...");
163        let extracted_path = self.patch_processor.extract_patch(&patch_path).await?;
164        progress_callback(0.45);
165
166        // 4. 验证解压后的文件结构
167        info!("Verifying patch file structure...");
168        self.validate_patch_structure(&extracted_path, operations)
169            .await?;
170        progress_callback(0.5);
171
172        // 5. 应用补丁操作
173        info!("Applying patch operations...");
174        self.apply_patch_operations(&extracted_path, operations, progress_callback)
175            .await?;
176
177        Ok(())
178    }
179
180    /// 验证补丁文件结构
181    async fn validate_patch_structure(
182        &self,
183        extracted_path: &Path,
184        operations: &PatchOperations,
185    ) -> Result<(), PatchExecutorError> {
186        // 收集所有需要的文件
187        let mut required_files = Vec::new();
188
189        // 添加需要替换的文件
190        if let Some(replace) = &operations.replace {
191            for file in &replace.files {
192                required_files.push(file.clone());
193            }
194            // 添加需要替换的目录(检查目录是否存在)
195            for dir in &replace.directories {
196                let dir_path = extracted_path.join(dir);
197                if !dir_path.exists() || !dir_path.is_dir() {
198                    return Err(PatchExecutorError::verification_failed(format!(
199                        "Required directory missing in patch: {dir}"
200                    )));
201                }
202            }
203        }
204
205        // 验证文件结构
206        self.patch_processor
207            .validate_extracted_structure(&required_files)
208            .await?;
209
210        debug!("Patch file structure verified");
211        Ok(())
212    }
213
214    /// 应用补丁操作
215    async fn apply_patch_operations<F>(
216        &mut self,
217        extracted_path: &Path,
218        operations: &PatchOperations,
219        progress_callback: &F,
220    ) -> Result<(), PatchExecutorError>
221    where
222        F: Fn(f64) + Send + Sync,
223    {
224        // 设置补丁源目录
225        self.file_executor.set_patch_source(extracted_path)?;
226
227        // 计算总操作数用于进度计算
228        let total_operations = operations.total_operations();
229
230        let mut completed_operations = 0;
231
232        let base_progress = 0.5; // 前面的步骤已经完成50%
233        let operations_progress_range = 0.5; // 操作占50%进度
234
235        // 执行文件替换
236        if let Some(replace) = &operations.replace {
237            // 如果有文件需要替换
238            if !replace.files.is_empty() {
239                info!("Replacing {} files", &replace.files.len());
240                self.file_executor.replace_files(&replace.files).await?;
241                completed_operations += replace.files.len();
242                let progress = base_progress
243                    + (completed_operations as f64 / total_operations as f64)
244                        * operations_progress_range;
245                progress_callback(progress);
246            }
247
248            // 执行目录替换
249            if !replace.directories.is_empty() {
250                info!("Replacing {} directories", &replace.directories.len());
251                self.file_executor
252                    .replace_directories(&replace.directories)
253                    .await?;
254                completed_operations += replace.directories.len();
255                let progress = base_progress
256                    + (completed_operations as f64 / total_operations as f64)
257                        * operations_progress_range;
258                progress_callback(progress);
259            }
260        }
261
262        // 执行删除操作
263        if let Some(delete) = &operations.delete {
264            // 如果有文件需要删除
265            if !delete.files.is_empty() {
266                info!("Deleting {} items", &delete.files.len());
267                self.file_executor.delete_items(&delete.files).await?;
268                completed_operations += &delete.files.len();
269                let progress = base_progress
270                    + (completed_operations as f64 / total_operations as f64)
271                        * operations_progress_range;
272                progress_callback(progress);
273            }
274            // 如果有目录需要删除
275            if !delete.directories.is_empty() {
276                info!("Deleting {} directories", &delete.directories.len());
277                self.file_executor.delete_items(&delete.directories).await?;
278                completed_operations += &delete.directories.len();
279                let progress = base_progress
280                    + (completed_operations as f64 / total_operations as f64)
281                        * operations_progress_range;
282                progress_callback(progress);
283            }
284        }
285
286        info!("Patch operations applied");
287        Ok(())
288    }
289
290    /// 回滚补丁操作
291    pub async fn rollback(&mut self) -> Result<(), PatchExecutorError> {
292        if !self.backup_enabled {
293            return Err(PatchExecutorError::BackupNotEnabled);
294        }
295
296        warn!("Starting rollback of patch operations...");
297        self.file_executor.rollback().await?;
298        info!("Patch rollback completed");
299        Ok(())
300    }
301
302    /// 获取工作目录
303    pub fn work_dir(&self) -> &Path {
304        &self.work_dir
305    }
306
307    /// 检查是否启用了备份
308    pub fn is_backup_enabled(&self) -> bool {
309        self.backup_enabled
310    }
311
312    /// 获取操作摘要
313    pub fn get_operation_summary(&self, operations: &PatchOperations) -> String {
314        let mut replace_file_count = 0;
315        let mut replace_dir_count = 0;
316        let mut delete_file_count = 0;
317        let mut delete_dir_count = 0;
318        if let Some(replace) = &operations.replace {
319            replace_file_count = replace.files.len();
320            replace_dir_count = replace.directories.len();
321        }
322        if let Some(delete) = &operations.delete {
323            delete_file_count = delete.files.len();
324            delete_dir_count = delete.directories.len();
325        }
326        let total = operations.total_operations();
327        format!(
328            "Patch operation summary: {} total operations (file replacements: {}, directory replacements: {}, file deletions: {}, directory deletions: {})",
329            total, replace_file_count, replace_dir_count, delete_file_count, delete_dir_count
330        )
331    }
332
333    /// 获取补丁处理器的临时目录(用于调试)
334    pub fn temp_dir(&self) -> &Path {
335        self.patch_processor.temp_dir()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use crate::api_types::ReplaceOperations;
343    use tempfile::TempDir;
344
345    #[tokio::test]
346    async fn test_patch_executor_creation() {
347        let temp_dir = TempDir::new().unwrap();
348        let executor = PatchExecutor::new(temp_dir.path().to_owned());
349        assert!(executor.is_ok());
350    }
351
352    #[tokio::test]
353    async fn test_enable_backup() {
354        let temp_dir = TempDir::new().unwrap();
355        let mut executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
356
357        assert!(!executor.is_backup_enabled());
358        let result = executor.enable_backup();
359        assert!(result.is_ok());
360        assert!(executor.is_backup_enabled());
361    }
362
363    #[tokio::test]
364    async fn test_validate_preconditions() {
365        let temp_dir = TempDir::new().unwrap();
366        let executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
367
368        // 测试有效的操作
369        let valid_operations = PatchOperations {
370            replace: Some(ReplaceOperations {
371                files: vec!["test.txt".to_string()],
372                directories: vec!["test_dir".to_string()],
373            }),
374            delete: Some(ReplaceOperations {
375                files: vec!["test.txt".to_string()],
376                directories: vec!["test_dir".to_string()],
377            }),
378        };
379
380        let result = executor.validate_preconditions(&valid_operations);
381        assert!(result.is_ok());
382
383        // 测试空操作
384        let empty_operations = PatchOperations {
385            replace: Some(ReplaceOperations {
386                files: vec![],
387                directories: vec![],
388            }),
389            delete: Some(ReplaceOperations {
390                files: vec![],
391                directories: vec![],
392            }),
393        };
394
395        let result = executor.validate_preconditions(&empty_operations);
396        assert!(result.is_err());
397    }
398
399    #[tokio::test]
400    async fn test_operation_summary() {
401        let temp_dir = TempDir::new().unwrap();
402        let executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
403
404        let operations = PatchOperations {
405            replace: Some(ReplaceOperations {
406                files: vec!["file1.txt".to_string(), "file2.txt".to_string()],
407                directories: vec!["dir1".to_string()],
408            }),
409            delete: Some(ReplaceOperations {
410                files: vec!["old_file.txt".to_string()],
411                directories: vec![],
412            }),
413        };
414
415        let summary = executor.get_operation_summary(&operations);
416        assert!(summary.contains("4 total operations"));
417        assert!(summary.contains("file replacements: 2"));
418        assert!(summary.contains("directory replacements: 1"));
419        assert!(summary.contains("deletions: 1"));
420    }
421
422    #[tokio::test]
423    async fn test_rollback_without_backup() {
424        let temp_dir = TempDir::new().unwrap();
425        let mut executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
426
427        // 测试未启用备份时的回滚
428        let result = executor.rollback().await;
429        assert!(result.is_err());
430        assert!(matches!(
431            result.unwrap_err(),
432            PatchExecutorError::BackupNotEnabled
433        ));
434    }
435}