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!("Preconditions validated, total {} operations", total_operations);
133        Ok(())
134    }
135
136    /// 执行补丁应用管道
137    async fn execute_patch_pipeline<F>(
138        &mut self,
139        patch_info: &PatchPackageInfo,
140        operations: &PatchOperations,
141        progress_callback: &F,
142    ) -> Result<(), PatchExecutorError>
143    where
144        F: Fn(f64) + Send + Sync,
145    {
146        // 1. 下载并验证补丁包
147        info!("Downloading patch package...");
148        let patch_path = self.patch_processor.download_patch(patch_info).await?;
149        progress_callback(0.25);
150
151        // 2. 验证补丁完整性和签名
152        info!("Verifying patch integrity...");
153        self.patch_processor
154            .verify_patch_integrity(&patch_path, patch_info)
155            .await?;
156        progress_callback(0.35);
157
158        // 3. 解压补丁包
159        info!("Extracting patch package...");
160        let extracted_path = self.patch_processor.extract_patch(&patch_path).await?;
161        progress_callback(0.45);
162
163        // 4. 验证解压后的文件结构
164        info!("Verifying patch file structure...");
165        self.validate_patch_structure(&extracted_path, operations)
166            .await?;
167        progress_callback(0.5);
168
169        // 5. 应用补丁操作
170        info!("Applying patch operations...");
171        self.apply_patch_operations(&extracted_path, operations, progress_callback)
172            .await?;
173
174        Ok(())
175    }
176
177    /// 验证补丁文件结构
178    async fn validate_patch_structure(
179        &self,
180        extracted_path: &Path,
181        operations: &PatchOperations,
182    ) -> Result<(), PatchExecutorError> {
183        // 收集所有需要的文件
184        let mut required_files = Vec::new();
185
186        // 添加需要替换的文件
187        if let Some(replace) = &operations.replace {
188            for file in &replace.files {
189                required_files.push(file.clone());
190            }
191            // 添加需要替换的目录(检查目录是否存在)
192            for dir in &replace.directories {
193                let dir_path = extracted_path.join(dir);
194                if !dir_path.exists() || !dir_path.is_dir() {
195                    return Err(PatchExecutorError::verification_failed(format!(
196                        "Required directory missing in patch: {dir}"
197                    )));
198                }
199            }
200        }
201
202        // 验证文件结构
203        self.patch_processor
204            .validate_extracted_structure(&required_files)
205            .await?;
206
207        debug!("Patch file structure verified");
208        Ok(())
209    }
210
211    /// 应用补丁操作
212    async fn apply_patch_operations<F>(
213        &mut self,
214        extracted_path: &Path,
215        operations: &PatchOperations,
216        progress_callback: &F,
217    ) -> Result<(), PatchExecutorError>
218    where
219        F: Fn(f64) + Send + Sync,
220    {
221        // 设置补丁源目录
222        self.file_executor.set_patch_source(extracted_path)?;
223
224        // 计算总操作数用于进度计算
225        let total_operations = operations.total_operations();
226
227        let mut completed_operations = 0;
228
229        let base_progress = 0.5; // 前面的步骤已经完成50%
230        let operations_progress_range = 0.5; // 操作占50%进度
231
232        // 执行文件替换
233        if let Some(replace) = &operations.replace {
234            // 如果有文件需要替换
235            if !replace.files.is_empty() {
236                info!("Replacing {} files", &replace.files.len());
237                self.file_executor.replace_files(&replace.files).await?;
238                completed_operations += replace.files.len();
239                let progress = base_progress
240                    + (completed_operations as f64 / total_operations as f64)
241                        * operations_progress_range;
242                progress_callback(progress);
243            }
244
245            // 执行目录替换
246            if !replace.directories.is_empty() {
247                info!("Replacing {} directories", &replace.directories.len());
248                self.file_executor
249                    .replace_directories(&replace.directories)
250                    .await?;
251                completed_operations += replace.directories.len();
252                let progress = base_progress
253                    + (completed_operations as f64 / total_operations as f64)
254                        * operations_progress_range;
255                progress_callback(progress);
256            }
257        }
258
259        // 执行删除操作
260        if let Some(delete) = &operations.delete {
261            // 如果有文件需要删除
262            if !delete.files.is_empty() {
263                info!("Deleting {} items", &delete.files.len());
264                self.file_executor.delete_items(&delete.files).await?;
265                completed_operations += &delete.files.len();
266                let progress = base_progress
267                    + (completed_operations as f64 / total_operations as f64)
268                        * operations_progress_range;
269                progress_callback(progress);
270            }
271            // 如果有目录需要删除
272            if !delete.directories.is_empty() {
273                info!("Deleting {} directories", &delete.directories.len());
274                self.file_executor.delete_items(&delete.directories).await?;
275                completed_operations += &delete.directories.len();
276                let progress = base_progress
277                    + (completed_operations as f64 / total_operations as f64)
278                        * operations_progress_range;
279                progress_callback(progress);
280            }
281        }
282
283        info!("Patch operations applied");
284        Ok(())
285    }
286
287    /// 回滚补丁操作
288    pub async fn rollback(&mut self) -> Result<(), PatchExecutorError> {
289        if !self.backup_enabled {
290            return Err(PatchExecutorError::BackupNotEnabled);
291        }
292
293        warn!("Starting rollback of patch operations...");
294        self.file_executor.rollback().await?;
295        info!("Patch rollback completed");
296        Ok(())
297    }
298
299    /// 获取工作目录
300    pub fn work_dir(&self) -> &Path {
301        &self.work_dir
302    }
303
304    /// 检查是否启用了备份
305    pub fn is_backup_enabled(&self) -> bool {
306        self.backup_enabled
307    }
308
309    /// 获取操作摘要
310    pub fn get_operation_summary(&self, operations: &PatchOperations) -> String {
311        let mut replace_file_count = 0;
312        let mut replace_dir_count = 0;
313        let mut delete_file_count = 0;
314        let mut delete_dir_count = 0;
315        if let Some(replace) = &operations.replace {
316            replace_file_count = replace.files.len();
317            replace_dir_count = replace.directories.len();
318        }
319        if let Some(delete) = &operations.delete {
320            delete_file_count = delete.files.len();
321            delete_dir_count = delete.directories.len();
322        }
323        let total = operations.total_operations();
324        format!(
325            "Patch operation summary: {} total operations (file replacements: {}, directory replacements: {}, file deletions: {}, directory deletions: {})",
326            total, replace_file_count, replace_dir_count, delete_file_count, delete_dir_count
327        )
328    }
329
330    /// 获取补丁处理器的临时目录(用于调试)
331    pub fn temp_dir(&self) -> &Path {
332        self.patch_processor.temp_dir()
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::api_types::ReplaceOperations;
340    use tempfile::TempDir;
341
342    #[tokio::test]
343    async fn test_patch_executor_creation() {
344        let temp_dir = TempDir::new().unwrap();
345        let executor = PatchExecutor::new(temp_dir.path().to_owned());
346        assert!(executor.is_ok());
347    }
348
349    #[tokio::test]
350    async fn test_enable_backup() {
351        let temp_dir = TempDir::new().unwrap();
352        let mut executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
353
354        assert!(!executor.is_backup_enabled());
355        let result = executor.enable_backup();
356        assert!(result.is_ok());
357        assert!(executor.is_backup_enabled());
358    }
359
360    #[tokio::test]
361    async fn test_validate_preconditions() {
362        let temp_dir = TempDir::new().unwrap();
363        let executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
364
365        // 测试有效的操作
366        let valid_operations = PatchOperations {
367            replace: Some(ReplaceOperations {
368                files: vec!["test.txt".to_string()],
369                directories: vec!["test_dir".to_string()],
370            }),
371            delete: Some(ReplaceOperations {
372                files: vec!["test.txt".to_string()],
373                directories: vec!["test_dir".to_string()],
374            }),
375        };
376
377        let result = executor.validate_preconditions(&valid_operations);
378        assert!(result.is_ok());
379
380        // 测试空操作
381        let empty_operations = PatchOperations {
382            replace: Some(ReplaceOperations {
383                files: vec![],
384                directories: vec![],
385            }),
386            delete: Some(ReplaceOperations {
387                files: vec![],
388                directories: vec![],
389            }),
390        };
391
392        let result = executor.validate_preconditions(&empty_operations);
393        assert!(result.is_err());
394    }
395
396    #[tokio::test]
397    async fn test_operation_summary() {
398        let temp_dir = TempDir::new().unwrap();
399        let executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
400
401        let operations = PatchOperations {
402            replace: Some(ReplaceOperations {
403                files: vec!["file1.txt".to_string(), "file2.txt".to_string()],
404                directories: vec!["dir1".to_string()],
405            }),
406            delete: Some(ReplaceOperations {
407                files: vec!["old_file.txt".to_string()],
408                directories: vec![],
409            }),
410        };
411
412        let summary = executor.get_operation_summary(&operations);
413        assert!(summary.contains("4 total operations"));
414        assert!(summary.contains("file replacements: 2"));
415        assert!(summary.contains("directory replacements: 1"));
416        assert!(summary.contains("deletions: 1"));
417    }
418
419    #[tokio::test]
420    async fn test_rollback_without_backup() {
421        let temp_dir = TempDir::new().unwrap();
422        let mut executor = PatchExecutor::new(temp_dir.path().to_owned()).unwrap();
423
424        // 测试未启用备份时的回滚
425        let result = executor.rollback().await;
426        assert!(result.is_err());
427        assert!(matches!(
428            result.unwrap_err(),
429            PatchExecutorError::BackupNotEnabled
430        ));
431    }
432}