feat: 扫描整理并行化、WebSocket 实时进度与统计看板
- 移动阶段并行处理文件,按原子计数上报累计进度 - 扫描遍历阶段实时上报已发现文件数,界面不再卡在 0/0 - 新增 /api/scan/ws WebSocket 实时推送扫描状态;前端断线指数退避自动重连,并以低频轮询兜底 - 新增统计看板与媒体库筛选/排序,目录选择器路径不存在时回退到最近可用父目录 - 存放目录支持自定义并持久化,默认输出到 KMP已整理
This commit is contained in:
+27
-18
@@ -6,11 +6,11 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kenaito-media-picker/internal/probe"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
||||
)
|
||||
|
||||
// File 扫描到的文件信息
|
||||
@@ -30,14 +30,20 @@ type Result struct {
|
||||
|
||||
// Options 扫描选项
|
||||
type Options struct {
|
||||
RootDir string // 扫描根目录
|
||||
SkipDir string // 白名单输出目录,整目录跳过
|
||||
SkipNames []string // 按文件名跳过(数据库、ffprobe 等)
|
||||
RootDir string // 扫描根目录
|
||||
SkipDir string // 白名单输出目录,整目录跳过
|
||||
SkipDirNames []string // 按目录名跳过(如 KMP已整理),任意位置命中即跳过
|
||||
SkipNames []string // 按文件名跳过(数据库、ffprobe 等)
|
||||
OnProgress func(done, total int)
|
||||
}
|
||||
|
||||
// Scan 递归扫描目录并读取每个文件的 MIME 信息
|
||||
func Scan(opts Options) (*Result, error) {
|
||||
res := &Result{}
|
||||
// 遍历阶段尚不知道总文件数,先按「已发现文件数」实时上报(total=0),
|
||||
// 并节流上报频率,避免目录很大时界面长时间无动静或推送过密。
|
||||
walkCount := 0
|
||||
lastReport := time.Now()
|
||||
err := filepath.Walk(opts.RootDir, func(currentPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// 单个路径访问失败(权限不足/损坏)不中止整体扫描,仅告警后跳过该子树
|
||||
@@ -48,6 +54,11 @@ func Scan(opts Options) (*Result, error) {
|
||||
if opts.SkipDir != "" && currentPath == opts.SkipDir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
for _, n := range opts.SkipDirNames {
|
||||
if info.Name() == n {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, n := range opts.SkipNames {
|
||||
@@ -64,6 +75,11 @@ func Scan(opts Options) (*Result, error) {
|
||||
Name: info.Name(),
|
||||
Ext: filepath.Ext(currentPath),
|
||||
})
|
||||
walkCount++
|
||||
if opts.OnProgress != nil && time.Since(lastReport) >= 100*time.Millisecond {
|
||||
lastReport = time.Now()
|
||||
opts.OnProgress(walkCount, 0)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -72,15 +88,16 @@ func Scan(opts Options) (*Result, error) {
|
||||
|
||||
total := len(res.Files)
|
||||
fmt.Printf("=== 文件总数: %d \n", total)
|
||||
bar := goPrint.NewBar(100)
|
||||
bar.SetNotice("=== 扫描文件Mime:")
|
||||
bar.SetGraph(">")
|
||||
bar.SetNoticeColor(goPrint.FontColor.Red)
|
||||
// 遍历结束、开始 MIME 探测前,上报一次完整总数
|
||||
if opts.OnProgress != nil {
|
||||
opts.OnProgress(total, total)
|
||||
}
|
||||
for i := range res.Files {
|
||||
res.Files[i].Mime = detectMime(res.Files[i].Path)
|
||||
bar.PrintBar(percent(i+1, total))
|
||||
if opts.OnProgress != nil {
|
||||
opts.OnProgress(i+1, total)
|
||||
}
|
||||
}
|
||||
bar.PrintEnd("=== Finish")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -138,11 +155,3 @@ func isPotentialMedia(fileMime string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// percent 计算百分比(0~100),total 为 0 时返回 100
|
||||
func percent(part, total int) int {
|
||||
if total <= 0 {
|
||||
return 100
|
||||
}
|
||||
return int(float64(part) / float64(total) * 100)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user