Reserve recording paths atomically
This commit is contained in:
committed by
Brandon McGinty
parent
43d7addc15
commit
9fdcf6171d
+24
-1
@@ -57,14 +57,19 @@ func New(directory string, format string, now time.Time, frameSize int, interval
|
||||
if err := os.MkdirAll(directory, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := UniquePath(directory, now, format)
|
||||
path, err := reservePath(directory, now, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := ffmpegArgs(format, path)
|
||||
cmd := exec.Command("ffmpeg", args...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
return nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
_ = os.Remove(path)
|
||||
return nil, err
|
||||
}
|
||||
recorder := &Recorder{
|
||||
@@ -91,6 +96,24 @@ func NormalizeFormat(format string) string {
|
||||
return format
|
||||
}
|
||||
|
||||
func reservePath(directory string, now time.Time, format string) (string, error) {
|
||||
for {
|
||||
path := UniquePath(directory, now, format)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(path)
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
func UniquePath(directory string, now time.Time, format string) string {
|
||||
base := fmt.Sprintf("barnard-recording-%s", now.Format("20060102-150405"))
|
||||
path := filepath.Join(directory, base+"."+format)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -58,6 +59,39 @@ func TestUniquePathAvoidsCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservePathPreventsConcurrentRecordingCollisions(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
now := time.Date(2026, 5, 14, 12, 30, 0, 0, time.Local)
|
||||
paths := make(chan string, 2)
|
||||
errs := make(chan error, 2)
|
||||
var wg sync.WaitGroup
|
||||
for range 2 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
path, err := reservePath(dir, now, "flac")
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
paths <- path
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(paths)
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var reserved []string
|
||||
for path := range paths {
|
||||
reserved = append(reserved, path)
|
||||
}
|
||||
if len(reserved) != 2 || reserved[0] == reserved[1] {
|
||||
t.Fatalf("reserved paths = %#v", reserved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeStereoFrame(t *testing.T) {
|
||||
mono := NormalizeStereoFrame([]int16{1, -2}, 3)
|
||||
wantMono := []int16{1, 1, -2, -2, 0, 0}
|
||||
|
||||
Reference in New Issue
Block a user