mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-09-06 16:06:45 +08:00
feat(backup): 新增按需(选择性)文件恢复 (#91)
在内容浏览基础上支持仅恢复勾选的文件/目录到原位置。FileRunner.Restore 按选中集合过滤提取与删除;RestoreService.StartSelective(Start 委托,零破坏);恢复端点接受可选 selectedPaths;前端内容弹窗支持勾选恢复。
This commit is contained in:
@@ -207,6 +207,10 @@ func (r *FileRunner) Restore(_ context.Context, task TaskSpec, artifactPath stri
|
||||
if cleanName == "." || cleanName == "" {
|
||||
continue
|
||||
}
|
||||
// 选择性恢复:仅提取被选中的文件/目录(及其子项)。
|
||||
if len(task.SelectedPaths) > 0 && !pathSelected(cleanName, task.SelectedPaths) {
|
||||
continue
|
||||
}
|
||||
targetPath, ok := resolveWithinParent(targetParent, cleanName)
|
||||
if !ok {
|
||||
return fmt.Errorf("tar entry escapes restore path")
|
||||
@@ -233,6 +237,10 @@ func (r *FileRunner) Restore(_ context.Context, task TaskSpec, artifactPath stri
|
||||
}
|
||||
}
|
||||
}
|
||||
// 选择性恢复时仅对选中范围应用删除,避免误删未选中的文件。
|
||||
if len(task.SelectedPaths) > 0 {
|
||||
pendingDeletions = filterSelectedPaths(pendingDeletions, task.SelectedPaths)
|
||||
}
|
||||
if err := applyDeletions(targetParent, pendingDeletions, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -240,6 +248,31 @@ func (r *FileRunner) Restore(_ context.Context, task TaskSpec, artifactPath stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// pathSelected 判断归档条目名是否落在选中集合内(精确匹配或位于选中目录之下)。
|
||||
func pathSelected(name string, selected []string) bool {
|
||||
for _, sel := range selected {
|
||||
clean := path.Clean(strings.TrimSpace(sel))
|
||||
if clean == "" || clean == "." {
|
||||
continue
|
||||
}
|
||||
if name == clean || strings.HasPrefix(name, clean+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// filterSelectedPaths 仅保留落在选中集合内的路径。
|
||||
func filterSelectedPaths(paths []string, selected []string) []string {
|
||||
filtered := make([]string, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
if pathSelected(path.Clean(strings.TrimSpace(p)), selected) {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// resolveWithinParent 将归档相对名安全解析为 targetParent 下的绝对路径;
|
||||
// 越界(路径穿越)时返回 ok=false。提取与删除共用此校验,杜绝逃逸。
|
||||
func resolveWithinParent(targetParent, name string) (string, bool) {
|
||||
|
||||
Reference in New Issue
Block a user