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