diff --git a/internal/organize/organize.go b/internal/organize/organize.go index cf3fab7..6188679 100644 --- a/internal/organize/organize.go +++ b/internal/organize/organize.go @@ -280,8 +280,8 @@ type imageProbeResult struct { // 只读数据库(查指纹),这是安全的。 func (o *Organizer) probeImage(f scanner.File) imageProbeResult { r := imageProbeResult{} - // PSD 不体检,直接标记"原样收走" - if strings.EqualFold(f.Ext, ".psd") { + // PSD/PSB 设计稿不体检,直接标记"原样收走" + if strings.EqualFold(f.Ext, ".psd") || strings.EqualFold(f.Ext, ".psb") { r.psd = true return r } diff --git a/internal/probe/exif.go b/internal/probe/exif.go index 4083a3c..0eb0ebf 100644 --- a/internal/probe/exif.go +++ b/internal/probe/exif.go @@ -71,13 +71,21 @@ func RotateImage(ffmpegPath, filePath string) error { } } - // 用转正后的文件替换原文件 - if err := os.Remove(filePath); err != nil { + // 用转正后的文件替换原文件:先把原文件改名成备份,再让转正结果顶上,最后删备份。 + // 这样即使中途失败,原文件也还在(只是改了名),不会弄丢。 + backup := filePath + ".rotate.bak" + if err := os.Rename(filePath, backup); err != nil { + _ = os.Remove(tmp) + return fmt.Errorf("备份原文件失败: %v", err) + } + if err := os.Rename(tmp, filePath); err != nil { + // 转正结果放不回去:把备份挪回来尽量恢复原状 + _ = os.Rename(backup, filePath) _ = os.Remove(tmp) return fmt.Errorf("替换原文件失败: %v", err) } - if err := os.Rename(tmp, filePath); err != nil { - return fmt.Errorf("替换原文件失败: %v", err) + if err := os.Remove(backup); err != nil { + fmt.Printf("=== 删除原文件备份失败(可手动清理 %s): %v\n", backup, err) } return nil } @@ -120,7 +128,7 @@ func readExifAPP1(filePath string) ([]byte, error) { if segLen < 2 { break } - if marker == 0xE1 && segLen >= 8 && string(buf[pos+4:pos+8]) == "Exif" { + if marker == 0xE1 && segLen >= 8 && pos+8 <= len(buf) && string(buf[pos+4:pos+8]) == "Exif" { end := pos + 2 + segLen if end > len(buf) { end = len(buf) @@ -254,7 +262,7 @@ func stripExifAPP1(rest []byte) []byte { out = append(out, rest[pos:]...) return out } - isExif := marker == 0xE1 && segLen >= 8 && string(rest[pos+4:pos+8]) == "Exif" + isExif := marker == 0xE1 && segLen >= 8 && pos+8 <= len(rest) && string(rest[pos+4:pos+8]) == "Exif" if !isExif { out = append(out, rest[pos:pos+2+segLen]...) } diff --git a/internal/service/scan.go b/internal/service/scan.go index 467b656..9980395 100644 --- a/internal/service/scan.go +++ b/internal/service/scan.go @@ -17,6 +17,7 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime/debug" "strings" "sync" "time" @@ -327,6 +328,18 @@ func (s *ScanService) run(rootDir, destDir string) { } }() + // 收尾必须恢复 stdout 并关闭管道,否则整个进程后续输出都会丢进这根无人读的管道里。 + // 用 defer 保证即使 runScan 中途 panic 也不会留下这种状态;panic 还会被接住, + // 转成"出错"状态推给前端,而不是让整个程序崩溃。 + defer func() { + os.Stdout = oldStdout + _ = w.Close() + <-done // 等读日志的 goroutine 把剩下的日志读完 + if rcv := recover(); rcv != nil { + s.finish(ScanError, fmt.Sprintf("扫描整理内部错误: %v\n%s", rcv, debug.Stack())) + } + }() + // 真正开始干活。期间程序里所有 fmt.Println/Printf 都会进上面的管道 scanErr := s.runScan(rootDir, destDir)