package main import ( "crypto/sha256" "encoding/hex" "errors" "fmt" "os" "path/filepath" ) const ( voiceDirName = "voices" voiceMaxBytes = 20 << 20 ) var errVoiceSize = errors.New("voice note is empty or exceeds 20 MB") // VoiceStore holds immutable audio shared by all agents. Names are hashes of // the received bytes, not sender filenames. The decoder detects the format. type VoiceStore struct { dir string } // Save publishes private audio atomically before its message is acknowledged. // Only nonempty input up to voiceMaxBytes is stored. func (s *VoiceStore) Save(data []byte) (name string, err error) { if len(data) == 0 || len(data) > voiceMaxBytes { return "", errVoiceSize } sum := sha256.Sum256(data) name = hex.EncodeToString(sum[:]) + ".audio" err = os.MkdirAll(s.dir, 0o700) if err != nil { return "", fmt.Errorf("create voice directory: %w", err) } file, err := os.CreateTemp(s.dir, ".voice-*") if err != nil { return "", fmt.Errorf("create voice file: %w", err) } defer func() { err = errors.Join(err, os.Remove(file.Name())) }() _, err = file.Write(data) if err == nil { err = file.Sync() } err = errors.Join(err, file.Close()) if err != nil { return "", fmt.Errorf("write voice: %w", err) } err = os.Link(file.Name(), filepath.Join(s.dir, name)) if err != nil && !errors.Is(err, os.ErrExist) { return "", fmt.Errorf("publish voice: %w", err) } return name, nil }