feat: 扫描整理支持中止;存放目录始终创建 KMP已整理 层
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
package organize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -59,6 +60,7 @@ type Organizer struct {
|
||||
ffmpegPath string // ffmpeg 程序路径;为空表示没有转正 EXIF 旋转图片的能力
|
||||
store *store.Store // 数据库;为 nil 表示不记录媒体信息
|
||||
mover *mover.Mover // 负责实际搬文件的家伙
|
||||
ctx context.Context // 中止信号;ctx 被取消时探测/归类立即停下
|
||||
buckets map[string][]string // 目标目录 → 里面该放哪些文件(只记源路径)
|
||||
order []string // 目标目录的先后顺序(保证输出稳定、不来回跳)
|
||||
onProgress func(phase string, done, total int)
|
||||
@@ -66,13 +68,15 @@ type Organizer struct {
|
||||
}
|
||||
|
||||
// New 创建整理器。store 可以为 nil——数据库打不开时,整理照常进行,只是不记入库信息。
|
||||
func New(pickerDir, ffprobePath, ffmpegPath string, st *store.Store) *Organizer {
|
||||
// ctx 可以为 nil——不提供中止能力,任务跑到完为止。
|
||||
func New(pickerDir, ffprobePath, ffmpegPath string, st *store.Store, ctx context.Context) *Organizer {
|
||||
return &Organizer{
|
||||
pickerDir: pickerDir,
|
||||
ffprobePath: ffprobePath,
|
||||
ffmpegPath: ffmpegPath,
|
||||
store: st,
|
||||
mover: &mover.Mover{},
|
||||
ctx: ctx,
|
||||
buckets: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
@@ -104,7 +108,9 @@ func workerCount() int {
|
||||
// 通俗理解:一条流水线有 N 个工人,每个工人从传送带上拿一个活干完,
|
||||
// 干完把结果按编号放回架子上(results[i] 对应 items[i] 的结果)。
|
||||
// onProgress 每干完一个活就报一次数(已完成几个),线程安全、只会涨不会退。
|
||||
func runParallel[T any, R any](items []T, fn func(T) R, onProgress func(done int)) []R {
|
||||
// ctx 被取消时工人不再干活(任务照常消费,避免发送方堵死),
|
||||
// 被跳过的位置留零值,调用方看到 ctx.Err() 会自行停下。
|
||||
func runParallel[T any, R any](ctx context.Context, items []T, fn func(T) R, onProgress func(done int)) []R {
|
||||
results := make([]R, len(items))
|
||||
if len(items) == 0 {
|
||||
return results
|
||||
@@ -121,6 +127,10 @@ func runParallel[T any, R any](items []T, fn func(T) R, onProgress func(done int
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := range jobs {
|
||||
// 用户点了中止:不再干活,但任务还要继续消费,免得主 goroutine 堵死在发送上
|
||||
if ctx != nil && ctx.Err() != nil {
|
||||
continue
|
||||
}
|
||||
results[i] = fn(items[i])
|
||||
if onProgress != nil {
|
||||
onProgress(int(atomic.AddInt32(&doneCount, 1)))
|
||||
@@ -350,10 +360,15 @@ func (o *Organizer) ProcessImages(images []scanner.File) {
|
||||
// 统计结果,最后打一行总结给用户看
|
||||
success, fail, ignore, dup := 0, 0, 0, 0
|
||||
total := len(images)
|
||||
results := runParallel(images, o.probeImage, func(done int) {
|
||||
results := runParallel(o.ctx, images, o.probeImage, func(done int) {
|
||||
o.report(PhaseImages, done, total)
|
||||
})
|
||||
for i, r := range results {
|
||||
// 用户点了中止:停下归类,已分堆的交给 runScan 决定要不要搬
|
||||
if o.ctx != nil && o.ctx.Err() != nil {
|
||||
fmt.Println("=== 扫描已中止,跳过剩余图片归类")
|
||||
break
|
||||
}
|
||||
f := images[i]
|
||||
switch {
|
||||
case r.psd:
|
||||
@@ -418,10 +433,15 @@ func (o *Organizer) probeMedia(f scanner.File) mediaProbeResult {
|
||||
func (o *Organizer) ProcessMedia(media []scanner.File) {
|
||||
video, audio, fail, ignore, dup := 0, 0, 0, 0, 0
|
||||
total := len(media)
|
||||
results := runParallel(media, o.probeMedia, func(done int) {
|
||||
results := runParallel(o.ctx, media, o.probeMedia, func(done int) {
|
||||
o.report(PhaseMedia, done, total)
|
||||
})
|
||||
for i, r := range results {
|
||||
// 用户点了中止:停下归类,已分堆的交给 runScan 决定要不要搬
|
||||
if o.ctx != nil && o.ctx.Err() != nil {
|
||||
fmt.Println("=== 扫描已中止,跳过剩余媒体归类")
|
||||
break
|
||||
}
|
||||
f := media[i]
|
||||
// ---------- 体检失败 ----------
|
||||
if r.err != nil {
|
||||
|
||||
Reference in New Issue
Block a user