- main.go 改为 Web 服务入口(Gin + 内嵌前端 dist + 自动打开浏览器) - internal/service: 新增扫描整理后台任务,支持状态机与实时日志捕获 - internal/api: 新增统一响应、媒体列表/统计、扫描开始/状态接口 - internal/store: 追加 ListMedia/CountMedia/Stats 查询,MediaInfo 补 JSON tag - internal/config: application.properties 配置加载(端口/数据库/自动开浏览器) - frontend: Vue2 + ElementUI + Vite 工程,含扫描整理/统计看板/媒体库三页 - build.sh 整合前端构建,输出单一 kenaito-media-picker.exe
103 lines
2.7 KiB
Go
103 lines
2.7 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{}
|
||
|
||
// moveResult 移动结果状态
|
||
type moveResult int
|
||
|
||
const (
|
||
moveOK moveResult = iota // 移动成功(rename 或 copy+delete 完整完成)
|
||
movePartial // 复制成功但删除源失败,源与目标同时存在
|
||
moveFailed // 移动/复制失败
|
||
)
|
||
|
||
// 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 {
|
||
switch moveOne(src, targetDir) {
|
||
case moveOK, movePartial:
|
||
moved[src] = targetDir
|
||
}
|
||
bar.PrintBar(percent(i+1, total))
|
||
}
|
||
bar.PrintEnd("=== Finish")
|
||
return moved
|
||
}
|
||
|
||
// moveOne 移动单个文件;跨文件系统时 fallback 为 copy+delete
|
||
func moveOne(src, targetDir string) moveResult {
|
||
fileName := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + "_" + filepath.Base(src)
|
||
target := filepath.Join(targetDir, fileName)
|
||
if err := os.Rename(src, target); err == nil {
|
||
return moveOK
|
||
}
|
||
if err := copyFile(src, target); err != nil {
|
||
fmt.Printf("=== 复制文件失败, 源: %s, 目标: %s, 错误: %v\n", src, target, err)
|
||
return moveFailed
|
||
}
|
||
if err := os.Remove(src); err != nil {
|
||
fmt.Printf("=== 警告: 复制成功但删除源文件失败, 源与目标同时存在, 源: %s, 错误: %v\n", src, err)
|
||
return movePartial
|
||
}
|
||
return moveOK
|
||
}
|
||
|
||
// 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)
|
||
}
|