keep the tone test output open across a reconnect

The saver reserves its output file exclusively, so opening it a second time
fails. connect opened a new one per connection and a disconnect closed it,
which meant the first reconnect died with "file exists" instead of resuming.

Open the saver once and re-attach it on reconnect, and close it on exit
instead. Detaching and closing are now separate, since only shutdown wants
both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brandon McGinty
2026-08-24 12:32:21 -04:00
co-authored by Claude Opus 5
parent 77fad24560
commit 5cdb2684b5
3 changed files with 38 additions and 14 deletions
+10 -1
View File
@@ -116,11 +116,20 @@ func (b *Barnard) cleanupConnectionAudio() {
b.connectionMutex.Unlock() b.connectionMutex.Unlock()
} }
func (b *Barnard) cleanupToneTestAudio() { // detachToneTestAudio unsubscribes the saver without closing its output, so a
// reconnect can re-attach the same file. The saver's output is opened
// exclusively and cannot be reopened.
func (b *Barnard) detachToneTestAudio() {
if b.toneTestSaverDetach != nil { if b.toneTestSaverDetach != nil {
b.toneTestSaverDetach.Detach() b.toneTestSaverDetach.Detach()
b.toneTestSaverDetach = nil b.toneTestSaverDetach = nil
} }
}
// cleanupToneTestAudio detaches the saver and closes its output. Use it when
// the client is shutting down, not between connections.
func (b *Barnard) cleanupToneTestAudio() {
b.detachToneTestAudio()
if b.toneTestSaver != nil { if b.toneTestSaver != nil {
b.toneTestSaver.Stop() b.toneTestSaver.Stop()
b.toneTestSaver = nil b.toneTestSaver = nil
+20 -9
View File
@@ -61,15 +61,24 @@ func (b *Barnard) connect(reconnect bool) bool {
// --- Tone test mode: skip all OpenAL; generate 440 Hz tone // --- Tone test mode: skip all OpenAL; generate 440 Hz tone
// --- and save incoming audio to a file. // --- and save incoming audio to a file.
// Open the output first. Starting transmission before this succeeds // The output is reserved exclusively, so it can only be opened once.
// leaves an orphaned tone goroutine when the path is unusable. // A reconnect keeps writing to the saver opened for the first
saver, err := NewAudioFileSaver(b.ToneTestOutput) // connection instead of failing on the file that already exists.
if err != nil { if b.toneTestSaver == nil {
b.exitWithError(err) // Open the output first. Starting transmission before this
return false // succeeds leaves an orphaned tone goroutine when the path is
// unusable.
saver, err := NewAudioFileSaver(b.ToneTestOutput)
if err != nil {
b.exitWithError(err)
return false
}
b.toneTestSaver = saver
} }
b.toneTestSaver = saver // Detach any registration left over from the previous connection so
b.toneTestSaverDetach = b.Client.Config.AttachAudio(saver) // the shared audio listener list does not grow once per reconnect.
b.detachToneTestAudio()
b.toneTestSaverDetach = b.Client.Config.AttachAudio(b.toneTestSaver)
b.setConnected(true) b.setConnected(true)
if b.toneTestAutoTransmit() { if b.toneTestAutoTransmit() {
@@ -225,7 +234,9 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
close(b.toneTestStop) close(b.toneTestStop)
b.toneTestStop = nil b.toneTestStop = nil
} }
b.cleanupToneTestAudio() // Keep the saver's output open: it was reserved exclusively and a
// reconnect re-attaches the same file. It is closed on exit.
b.detachToneTestAudio()
} }
b.Notify("disconnect", "me", reason) b.Notify("disconnect", "me", reason)
+8 -4
View File
@@ -445,15 +445,19 @@ func (b *Barnard) OnMicVolumeUp(ui *uiterm.Ui, key uiterm.Key) {
} }
func (b *Barnard) OnQuitPress(ui *uiterm.Ui, key uiterm.Key) { func (b *Barnard) OnQuitPress(ui *uiterm.Ui, key uiterm.Key) {
b.stopReconnects() b.shutdown()
b.StopRecordingIfActive(true)
b.Client.Disconnect()
b.Ui.Close()
} }
func (b *Barnard) CommandExit(ui *uiterm.Ui, cmd string) { func (b *Barnard) CommandExit(ui *uiterm.Ui, cmd string) {
b.shutdown()
}
// shutdown releases everything that outlives a single connection, including
// the tone test saver's output file, which reconnects deliberately keep open.
func (b *Barnard) shutdown() {
b.stopReconnects() b.stopReconnects()
b.StopRecordingIfActive(true) b.StopRecordingIfActive(true)
b.cleanupToneTestAudio()
b.Client.Disconnect() b.Client.Disconnect()
b.Ui.Close() b.Ui.Close()
} }