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 {
|
if err := os.MkdirAll(directory, 0755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path := UniquePath(directory, now, format)
|
path, err := reservePath(directory, now, format)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
args := ffmpegArgs(format, path)
|
args := ffmpegArgs(format, path)
|
||||||
cmd := exec.Command("ffmpeg", args...)
|
cmd := exec.Command("ffmpeg", args...)
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = os.Remove(path)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
|
_ = os.Remove(path)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
recorder := &Recorder{
|
recorder := &Recorder{
|
||||||
@@ -91,6 +96,24 @@ func NormalizeFormat(format string) string {
|
|||||||
return format
|
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 {
|
func UniquePath(directory string, now time.Time, format string) string {
|
||||||
base := fmt.Sprintf("barnard-recording-%s", now.Format("20060102-150405"))
|
base := fmt.Sprintf("barnard-recording-%s", now.Format("20060102-150405"))
|
||||||
path := filepath.Join(directory, base+"."+format)
|
path := filepath.Join(directory, base+"."+format)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func TestNormalizeStereoFrame(t *testing.T) {
|
||||||
mono := NormalizeStereoFrame([]int16{1, -2}, 3)
|
mono := NormalizeStereoFrame([]int16{1, -2}, 3)
|
||||||
wantMono := []int16{1, 1, -2, -2, 0, 0}
|
wantMono := []int16{1, 1, -2, -2, 0, 0}
|
||||||
|
|||||||
Reference in New Issue
Block a user