audio_pipeline
This commit is contained in:
144
watcher/internal/scanner/scanner.go
Normal file
144
watcher/internal/scanner/scanner.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid/v2"
|
||||
)
|
||||
|
||||
var allowedExts = map[string]bool{
|
||||
".mp3": true, ".wav": true, ".m4a": true,
|
||||
".ogg": true, ".flac": true, ".webm": true,
|
||||
}
|
||||
|
||||
type ClaimedFile struct {
|
||||
TaskID string
|
||||
FilePath string
|
||||
Filename string
|
||||
Size int64
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
StorageRoot string
|
||||
IncomingDir string
|
||||
ProcessingDir string
|
||||
FailedDir string
|
||||
StableWindow time.Duration
|
||||
StableChecks int
|
||||
}
|
||||
|
||||
type Scanner struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func New(cfg Config) *Scanner {
|
||||
return &Scanner{cfg: cfg}
|
||||
}
|
||||
|
||||
func (s *Scanner) EnsureDirs() error {
|
||||
for _, dir := range []string{s.cfg.IncomingDir, s.cfg.ProcessingDir, s.cfg.FailedDir} {
|
||||
if err := os.MkdirAll(filepath.Join(s.cfg.StorageRoot, dir), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scanner) ScanOnce() ([]ClaimedFile, error) {
|
||||
incoming := filepath.Join(s.cfg.StorageRoot, s.cfg.IncomingDir)
|
||||
entries, err := os.ReadDir(incoming)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var claimed []ClaimedFile
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := e.Name()
|
||||
if strings.HasPrefix(name, ".") || strings.HasSuffix(strings.ToLower(name), ".tmp") {
|
||||
continue
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
if !allowedExts[ext] {
|
||||
continue
|
||||
}
|
||||
src := filepath.Join(incoming, name)
|
||||
if !s.isStable(src) {
|
||||
continue
|
||||
}
|
||||
cf, err := s.claim(src, name, ext)
|
||||
if err != nil {
|
||||
slog.Warn("claim failed", "file", name, "error", err)
|
||||
continue
|
||||
}
|
||||
claimed = append(claimed, cf)
|
||||
}
|
||||
return claimed, nil
|
||||
}
|
||||
|
||||
func (s *Scanner) isStable(path string) bool {
|
||||
var lastSize int64 = -1
|
||||
for i := 0; i < s.cfg.StableChecks; i++ {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
size := info.Size()
|
||||
if lastSize >= 0 && size != lastSize {
|
||||
return false
|
||||
}
|
||||
lastSize = size
|
||||
if i < s.cfg.StableChecks-1 {
|
||||
time.Sleep(s.cfg.StableWindow)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Scanner) claim(src, originalName, ext string) (ClaimedFile, error) {
|
||||
info, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return ClaimedFile{}, err
|
||||
}
|
||||
taskID := ulid.Make().String()
|
||||
processing := filepath.Join(s.cfg.StorageRoot, s.cfg.ProcessingDir)
|
||||
dst := filepath.Join(processing, taskID+ext)
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
return ClaimedFile{}, fmt.Errorf("rename: %w", err)
|
||||
}
|
||||
slog.Info("claimed file", "task_id", taskID, "filename", originalName, "path", dst, "size", info.Size())
|
||||
return ClaimedFile{
|
||||
TaskID: taskID,
|
||||
FilePath: dst,
|
||||
Filename: originalName,
|
||||
Size: info.Size(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Scanner) RollbackToIncoming(filePath, originalName string) error {
|
||||
incoming := filepath.Join(s.cfg.StorageRoot, s.cfg.IncomingDir)
|
||||
dst := filepath.Join(incoming, originalName)
|
||||
if err := os.Rename(filePath, dst); err != nil {
|
||||
return s.MoveToFailed(filePath, originalName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scanner) MoveToFailed(filePath, originalName string) error {
|
||||
failed := filepath.Join(s.cfg.StorageRoot, s.cfg.FailedDir)
|
||||
if err := os.MkdirAll(failed, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
dst := filepath.Join(failed, originalName)
|
||||
return os.Rename(filePath, dst)
|
||||
}
|
||||
Reference in New Issue
Block a user