Skip to main content

client_core/patch_executor/
file_operations.rs

1// client-core/src/patch_executor/file_operations.rs
2//! 文件操作执行器
3//!
4//! 负责安全的文件替换、删除和回滚操作
5
6use super::error::{PatchExecutorError, Result};
7use fs_extra::dir;
8use remove_dir_all::remove_dir_all;
9use std::path::{Path, PathBuf};
10use tempfile::{NamedTempFile, TempDir};
11use tokio::fs;
12use tracing::{debug, info, warn};
13use walkdir::WalkDir;
14
15/// 文件操作执行器
16pub struct FileOperationExecutor {
17    /// 工作目录
18    work_dir: PathBuf,
19    /// 备份目录(用于回滚)
20    backup_dir: Option<TempDir>,
21    /// 补丁源目录
22    patch_source: Option<PathBuf>,
23}
24
25impl FileOperationExecutor {
26    /// 创建新的文件操作执行器
27    pub fn new(work_dir: PathBuf) -> Result<Self> {
28        if !work_dir.exists() {
29            return Err(PatchExecutorError::path_error(format!(
30                "Working directory does not exist: {work_dir:?}"
31            )));
32        }
33
34        debug!("Creating file operation executor, working directory: {:?}", work_dir);
35
36        Ok(Self {
37            work_dir,
38            backup_dir: None,
39            patch_source: None,
40        })
41    }
42
43    /// 启用备份模式(支持回滚)
44    pub fn enable_backup(&mut self) -> Result<()> {
45        self.backup_dir = Some(TempDir::new()?);
46        info!("File operation backup mode enabled");
47        Ok(())
48    }
49
50    /// 设置补丁源目录
51    pub fn set_patch_source(&mut self, patch_source: &Path) -> Result<()> {
52        if !patch_source.exists() {
53            return Err(PatchExecutorError::path_error(format!(
54                "Patch source directory does not exist: {patch_source:?}"
55            )));
56        }
57
58        self.patch_source = Some(patch_source.to_owned());
59        debug!("Setting patch source directory: {:?}", patch_source);
60        Ok(())
61    }
62
63    /// 执行文件替换操作
64    pub async fn replace_files(&self, files: &[String]) -> Result<()> {
65        info!("Starting to replace {} files", files.len());
66
67        for file_path in files {
68            self.replace_single_file(file_path).await?;
69        }
70
71        info!("File replacement completed");
72        Ok(())
73    }
74
75    /// 执行目录替换操作
76    pub async fn replace_directories(&self, directories: &[String]) -> Result<()> {
77        info!("Starting to replace {} directories", directories.len());
78
79        for dir_path in directories {
80            self.replace_single_directory(dir_path).await?;
81        }
82
83        info!("Directory replacement completed");
84        Ok(())
85    }
86
87    /// 执行删除操作
88    pub async fn delete_items(&self, items: &[String]) -> Result<()> {
89        info!("Starting to delete {} items", items.len());
90
91        for item_path in items {
92            self.delete_single_item(item_path).await?;
93        }
94
95        info!("Delete operation completed");
96        Ok(())
97    }
98
99    /// 替换单个文件
100    async fn replace_single_file(&self, file_path: &str) -> Result<()> {
101        let target_path = self.work_dir.join(file_path);
102
103        // 获取补丁源路径
104        let source_path = self.get_patch_source_path(file_path)?;
105
106        // 创建备份
107        if let Some(backup_dir) = &self.backup_dir {
108            if target_path.exists() {
109                let backup_path = backup_dir.path().join(file_path);
110                if let Some(parent) = backup_path.parent() {
111                    fs::create_dir_all(parent).await?;
112                }
113                fs::copy(&target_path, &backup_path).await?;
114                debug!("Backed up file: {} -> {:?}", file_path, backup_path);
115            }
116        }
117
118        // 原子性替换
119        self.atomic_file_replace(&source_path, &target_path).await?;
120
121        info!("File replaced: {}", file_path);
122        Ok(())
123    }
124
125    /// 替换单个目录
126    async fn replace_single_directory(&self, dir_path: &str) -> Result<()> {
127        let target_path = self.work_dir.join(dir_path);
128
129        // 获取补丁源路径
130        let source_path = self.get_patch_source_path(dir_path)?;
131
132        // 创建备份
133        if let Some(backup_dir) = &self.backup_dir {
134            if target_path.exists() {
135                let backup_path = backup_dir.path().join(dir_path);
136                self.backup_directory(&target_path, &backup_path).await?;
137                debug!("Backed up directory: {} -> {:?}", dir_path, backup_path);
138            }
139        }
140
141        // 删除目标目录
142        if target_path.exists() {
143            self.safe_remove_directory(&target_path).await?;
144        }
145
146        // 复制新目录
147        self.copy_directory(&source_path, &target_path).await?;
148
149        info!("Directory replaced: {}", dir_path);
150        Ok(())
151    }
152
153    /// 删除单个项目
154    async fn delete_single_item(&self, item_path: &str) -> Result<()> {
155        let target_path = self.work_dir.join(item_path);
156
157        if !target_path.exists() {
158            warn!("Delete target does not exist, skipping: {}", item_path);
159            return Ok(());
160        }
161
162        // 创建备份
163        if let Some(backup_dir) = &self.backup_dir {
164            let backup_path = backup_dir.path().join(item_path);
165            if target_path.is_dir() {
166                self.backup_directory(&target_path, &backup_path).await?;
167            } else {
168                if let Some(parent) = backup_path.parent() {
169                    fs::create_dir_all(parent).await?;
170                }
171                fs::copy(&target_path, &backup_path).await?;
172            }
173            debug!("Backed up item for deletion: {} -> {:?}", item_path, backup_path);
174        }
175
176        // 执行删除
177        if target_path.is_dir() {
178            self.safe_remove_directory(&target_path).await?;
179        } else {
180            fs::remove_file(&target_path).await?;
181        }
182
183        info!("Deleted: {}", item_path);
184        Ok(())
185    }
186
187    /// 获取补丁源文件路径
188    fn get_patch_source_path(&self, relative_path: &str) -> Result<PathBuf> {
189        let patch_source = self
190            .patch_source
191            .as_ref()
192            .ok_or(PatchExecutorError::PatchSourceNotSet)?;
193
194        let source_path = patch_source.join(relative_path);
195
196        if !source_path.exists() {
197            return Err(PatchExecutorError::path_error(format!(
198                "Patch source file does not exist: {source_path:?}"
199            )));
200        }
201
202        Ok(source_path)
203    }
204
205    /// 原子性文件替换
206    async fn atomic_file_replace(&self, source: &Path, target: &Path) -> Result<()> {
207        // 确保目标目录存在
208        if let Some(parent) = target.parent() {
209            fs::create_dir_all(parent).await?;
210        }
211
212        // 使用临时文件实现原子性替换
213        let temp_file = NamedTempFile::new_in(target.parent().unwrap_or_else(|| Path::new(".")))?;
214
215        // 复制内容
216        let source_content = fs::read(source).await?;
217        fs::write(temp_file.path(), source_content).await?;
218
219        // 原子性移动
220        temp_file.persist(target)?;
221
222        debug!("Atomic replacement completed: {:?} -> {:?}", source, target);
223        Ok(())
224    }
225
226    /// 安全删除目录(跨平台兼容)
227    async fn safe_remove_directory(&self, path: &Path) -> Result<()> {
228        let path_clone = path.to_owned();
229        tokio::task::spawn_blocking(move || remove_dir_all(&path_clone))
230            .await
231            .map_err(|e| PatchExecutorError::custom(format!("Delete directory task failed: {e}")))??;
232
233        debug!("Safely deleted directory: {:?}", path);
234        Ok(())
235    }
236
237    /// 复制目录
238    async fn copy_directory(&self, source: &Path, target: &Path) -> Result<()> {
239        let source_clone = source.to_owned();
240        let target_clone = target.to_owned();
241
242        tokio::task::spawn_blocking(move || {
243            let options = dir::CopyOptions::new().overwrite(true).copy_inside(true);
244
245            // 确保目标目录的父目录存在
246            if let Some(parent) = target_clone.parent() {
247                std::fs::create_dir_all(parent)
248                    .map_err(|e| PatchExecutorError::custom(format!("Failed to create target parent directory: {e}")))?;
249            }
250
251            // 如果目标目录不存在,创建它
252            if !target_clone.exists() {
253                std::fs::create_dir_all(&target_clone)
254                    .map_err(|e| PatchExecutorError::custom(format!("Failed to create target directory: {e}")))?;
255            }
256
257            // 复制源目录内容到目标目录
258            dir::copy(
259                &source_clone,
260                target_clone.parent().unwrap_or(&target_clone),
261                &options,
262            )
263            .map_err(|e| PatchExecutorError::custom(format!("Directory copy failed: {e}")))?;
264
265            Ok::<(), PatchExecutorError>(())
266        })
267        .await
268        .map_err(|e| PatchExecutorError::custom(format!("Failed to copy directory task: {e}")))??;
269
270        debug!("Directory copied: {:?} -> {:?}", source, target);
271        Ok(())
272    }
273
274    /// 备份目录
275    async fn backup_directory(&self, source: &Path, backup: &Path) -> Result<()> {
276        if let Some(parent) = backup.parent() {
277            fs::create_dir_all(parent).await?;
278        }
279
280        self.copy_directory(source, backup).await?;
281        debug!("Directory backed up: {:?} -> {:?}", source, backup);
282        Ok(())
283    }
284
285    /// 回滚操作
286    pub async fn rollback(&self) -> Result<()> {
287        if let Some(backup_dir) = &self.backup_dir {
288            warn!("Starting file operation rollback...");
289
290            // 遍历备份目录,恢复所有文件
291            let backup_path = backup_dir.path().to_owned();
292            let work_dir = self.work_dir.clone();
293
294            tokio::task::spawn_blocking(move || {
295                for entry in WalkDir::new(&backup_path) {
296                    let entry = entry.map_err(|e| {
297                        PatchExecutorError::custom(format!("Failed to traverse backup directory: {e}"))
298                    })?;
299
300                    let backup_file_path = entry.path();
301                    if backup_file_path.is_file() {
302                        // 计算相对路径
303                        let relative_path =
304                            backup_file_path.strip_prefix(&backup_path).map_err(|e| {
305                                PatchExecutorError::custom(format!("Failed to calculate relative path: {e}"))
306                            })?;
307
308                        let target_path = work_dir.join(relative_path);
309
310                        // 确保目标目录存在
311                        if let Some(parent) = target_path.parent() {
312                            std::fs::create_dir_all(parent).map_err(|e| {
313                                PatchExecutorError::custom(format!("Failed to create rollback target directory: {e}"))
314                            })?;
315                        }
316
317                        // 恢复文件
318                        std::fs::copy(backup_file_path, &target_path).map_err(|e| {
319                            PatchExecutorError::custom(format!("Failed to restore file: {e}"))
320                        })?;
321
322                        debug!("Restoring file: {:?} -> {:?}", backup_file_path, target_path);
323                    }
324                }
325
326                Ok::<(), PatchExecutorError>(())
327            })
328            .await
329            .map_err(|e| PatchExecutorError::custom(format!("Rollback task failed: {e}")))??;
330
331            info!("File operation rollback completed");
332        } else {
333            return Err(PatchExecutorError::BackupNotEnabled);
334        }
335
336        Ok(())
337    }
338
339    /// 获取工作目录
340    pub fn work_dir(&self) -> &Path {
341        &self.work_dir
342    }
343
344    /// 检查是否启用了备份
345    pub fn is_backup_enabled(&self) -> bool {
346        self.backup_dir.is_some()
347    }
348
349    /// 获取补丁源目录
350    pub fn patch_source(&self) -> Option<&Path> {
351        self.patch_source.as_deref()
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358    use tempfile::TempDir;
359    use tokio::fs;
360
361    #[tokio::test]
362    async fn test_file_operation_executor_creation() {
363        let temp_dir = TempDir::new().unwrap();
364        let executor = FileOperationExecutor::new(temp_dir.path().to_owned());
365        assert!(executor.is_ok());
366    }
367
368    #[tokio::test]
369    async fn test_enable_backup() {
370        let temp_dir = TempDir::new().unwrap();
371        let mut executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
372
373        assert!(!executor.is_backup_enabled());
374        let result = executor.enable_backup();
375        assert!(result.is_ok());
376        assert!(executor.is_backup_enabled());
377    }
378
379    #[tokio::test]
380    async fn test_invalid_work_dir() {
381        let invalid_path = PathBuf::from("/nonexistent/path");
382        let executor = FileOperationExecutor::new(invalid_path);
383        assert!(executor.is_err());
384    }
385
386    #[tokio::test]
387    async fn test_set_patch_source() {
388        let temp_dir = TempDir::new().unwrap();
389        let patch_source_dir = TempDir::new().unwrap();
390
391        let mut executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
392        let result = executor.set_patch_source(patch_source_dir.path());
393        assert!(result.is_ok());
394        assert_eq!(executor.patch_source(), Some(patch_source_dir.path()));
395    }
396
397    #[tokio::test]
398    async fn test_atomic_file_replace() {
399        let temp_dir = TempDir::new().unwrap();
400        let executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
401
402        // 创建源文件
403        let source_file = temp_dir.path().join("source.txt");
404        let content = "test content";
405        fs::write(&source_file, content).await.unwrap();
406
407        // 创建目标文件路径
408        let target_file = temp_dir.path().join("target.txt");
409
410        // 执行原子性替换
411        executor
412            .atomic_file_replace(&source_file, &target_file)
413            .await
414            .unwrap();
415
416        // 验证目标文件内容
417        let target_content = fs::read_to_string(&target_file).await.unwrap();
418        assert_eq!(target_content, content);
419    }
420
421    #[tokio::test]
422    async fn test_file_replacement_with_backup() {
423        let temp_dir = TempDir::new().unwrap();
424        let patch_source_dir = TempDir::new().unwrap();
425
426        let mut executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
427        executor.enable_backup().unwrap();
428        executor.set_patch_source(patch_source_dir.path()).unwrap();
429
430        // 创建原始文件
431        let original_file = temp_dir.path().join("test.txt");
432        let original_content = "original content";
433        fs::write(&original_file, original_content).await.unwrap();
434
435        // 创建补丁文件
436        let patch_file = patch_source_dir.path().join("test.txt");
437        let patch_content = "new content";
438        fs::write(&patch_file, patch_content).await.unwrap();
439
440        // 执行文件替换
441        executor
442            .replace_files(&["test.txt".to_string()])
443            .await
444            .unwrap();
445
446        // 验证文件已被替换
447        let new_content = fs::read_to_string(&original_file).await.unwrap();
448        assert_eq!(new_content, patch_content);
449
450        // 测试回滚
451        executor.rollback().await.unwrap();
452
453        // 验证文件已被恢复
454        let restored_content = fs::read_to_string(&original_file).await.unwrap();
455        assert_eq!(restored_content, original_content);
456    }
457
458    #[tokio::test]
459    async fn test_directory_operations() {
460        let temp_dir = TempDir::new().unwrap();
461        let patch_source_dir = TempDir::new().unwrap();
462
463        let mut executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
464        executor.enable_backup().unwrap();
465        executor.set_patch_source(patch_source_dir.path()).unwrap();
466
467        // 创建原始目录和文件
468        let original_dir = temp_dir.path().join("testdir");
469        fs::create_dir_all(&original_dir).await.unwrap();
470        fs::write(original_dir.join("file1.txt"), "original file1")
471            .await
472            .unwrap();
473
474        // 创建补丁目录和文件
475        let patch_dir = patch_source_dir.path().join("testdir");
476        fs::create_dir_all(&patch_dir).await.unwrap();
477        fs::write(patch_dir.join("file2.txt"), "new file2")
478            .await
479            .unwrap();
480
481        // 执行目录替换
482        executor
483            .replace_directories(&["testdir".to_string()])
484            .await
485            .unwrap();
486
487        // 验证目录已被替换
488        assert!(!original_dir.join("file1.txt").exists());
489        assert!(original_dir.join("file2.txt").exists());
490        let new_content = fs::read_to_string(original_dir.join("file2.txt"))
491            .await
492            .unwrap();
493        assert_eq!(new_content, "new file2");
494    }
495
496    #[tokio::test]
497    async fn test_delete_operations() {
498        let temp_dir = TempDir::new().unwrap();
499        let mut executor = FileOperationExecutor::new(temp_dir.path().to_owned()).unwrap();
500        executor.enable_backup().unwrap();
501
502        // 创建要删除的文件
503        let test_file = temp_dir.path().join("to_delete.txt");
504        fs::write(&test_file, "delete me").await.unwrap();
505
506        // 执行删除
507        executor
508            .delete_items(&["to_delete.txt".to_string()])
509            .await
510            .unwrap();
511
512        // 验证文件已被删除
513        assert!(!test_file.exists());
514
515        // 测试回滚
516        executor.rollback().await.unwrap();
517
518        // 验证文件已被恢复
519        assert!(test_file.exists());
520        let restored_content = fs::read_to_string(&test_file).await.unwrap();
521        assert_eq!(restored_content, "delete me");
522    }
523}