- scanner: filepath.Ext 替代 path.Ext,修复 Windows 点号目录名扩展名解析错误 - scanner: Walk 遇不可访问路径仅告警跳过,不再整体中止扫描 - probe: ffprobe 探测增加 30s 超时,避免损坏文件卡死程序 - mover: copyFile 失败时清理半截目标文件,消除双重 Close - 新增代码审查报告 bug20260814.md
93 lines
2.3 KiB
Go
93 lines
2.3 KiB
Go
// Package mover 负责文件的移动,跨文件系统时自动回退为复制后删除。
|
||
package mover
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
||
uuid "github.com/satori/go.uuid"
|
||
)
|
||
|
||
// Mover 文件移动器
|
||
type Mover struct{}
|
||
|
||
// MoveToDir 将一批文件移动到目标目录(目标文件名使用 uuid 前缀防止同名覆盖)。
|
||
// 返回移动成功的 源路径 → 目标目录 映射,供记录去向使用。
|
||
func (m *Mover) MoveToDir(filePaths []string, targetDir string) map[string]string {
|
||
moved := make(map[string]string)
|
||
if len(filePaths) == 0 {
|
||
return moved
|
||
}
|
||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||
fmt.Printf("=== 创建目录失败: %s, 错误: %v\n", targetDir, err)
|
||
return moved
|
||
}
|
||
|
||
bar := goPrint.NewBar(100)
|
||
bar.SetNotice("=== 移动文件到目录:")
|
||
bar.SetGraph(">")
|
||
total := len(filePaths)
|
||
for i, src := range filePaths {
|
||
if moveOne(src, targetDir) {
|
||
moved[src] = targetDir
|
||
}
|
||
bar.PrintBar(percent(i+1, total))
|
||
}
|
||
bar.PrintEnd("=== Finish")
|
||
return moved
|
||
}
|
||
|
||
// moveOne 移动单个文件;跨文件系统时 fallback 为 copy+delete
|
||
func moveOne(src, targetDir string) bool {
|
||
fileName := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + "_" + filepath.Base(src)
|
||
target := filepath.Join(targetDir, fileName)
|
||
if err := os.Rename(src, target); err == nil {
|
||
return true
|
||
}
|
||
if err := copyFile(src, target); err != nil {
|
||
fmt.Printf("=== 复制文件失败, 源: %s, 目标: %s, 错误: %v\n", src, target, err)
|
||
return false
|
||
}
|
||
if err := os.Remove(src); err != nil {
|
||
fmt.Printf("=== 删除源文件失败: %s, 错误: %v\n", src, err)
|
||
// 复制成功但删除失败,文件已存在于两处,视为移动成功
|
||
}
|
||
return true
|
||
}
|
||
|
||
// copyFile 逐块复制文件;失败时清理已写入的目标文件,避免残留半截文件
|
||
func copyFile(src, dst string) error {
|
||
in, err := os.Open(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer in.Close()
|
||
|
||
out, err := os.Create(dst)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err := io.Copy(out, in); err != nil {
|
||
out.Close()
|
||
_ = os.Remove(dst)
|
||
return err
|
||
}
|
||
if err := out.Close(); err != nil {
|
||
_ = os.Remove(dst)
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// percent 计算百分比(0~100)
|
||
func percent(part, total int) int {
|
||
if total <= 0 {
|
||
return 100
|
||
}
|
||
return int(float64(part) / float64(total) * 100)
|
||
}
|