internal refactor of notifications
This commit is contained in:
+13
-17
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/leonardotrapani/hyprvoice/internal/bus"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/daemon"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/notify"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -361,23 +362,18 @@ func runInteractiveConfig() error {
|
||||
input := strings.TrimSpace(strings.ToLower(scanner.Text()))
|
||||
if input == "y" || input == "yes" {
|
||||
fmt.Println()
|
||||
// Get current/default values for display
|
||||
recTitle, recBody := cfg.GetRecordingStarted()
|
||||
transTitle, transBody := cfg.GetTranscribing()
|
||||
reloadTitle, reloadBody := cfg.GetConfigReloaded()
|
||||
cancelTitle, cancelBody := cfg.GetOperationCancelled()
|
||||
abortRecBody := cfg.GetRecordingAborted()
|
||||
abortInjBody := cfg.GetInjectionAborted()
|
||||
// Get resolved values (user config merged with defaults)
|
||||
msgs := cfg.Notifications.Messages.Resolve()
|
||||
|
||||
// Recording Started
|
||||
fmt.Println(" Recording Started notification:")
|
||||
fmt.Printf(" Title (current: %s): ", recTitle)
|
||||
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgRecordingStarted].Title)
|
||||
if scanner.Scan() {
|
||||
if t := strings.TrimSpace(scanner.Text()); t != "" {
|
||||
cfg.Notifications.Messages.RecordingStarted.Title = t
|
||||
}
|
||||
}
|
||||
fmt.Printf(" Body (current: %s): ", recBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgRecordingStarted].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.RecordingStarted.Body = b
|
||||
@@ -387,13 +383,13 @@ func runInteractiveConfig() error {
|
||||
|
||||
// Transcribing
|
||||
fmt.Println(" Transcribing notification:")
|
||||
fmt.Printf(" Title (current: %s): ", transTitle)
|
||||
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgTranscribing].Title)
|
||||
if scanner.Scan() {
|
||||
if t := strings.TrimSpace(scanner.Text()); t != "" {
|
||||
cfg.Notifications.Messages.Transcribing.Title = t
|
||||
}
|
||||
}
|
||||
fmt.Printf(" Body (current: %s): ", transBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgTranscribing].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.Transcribing.Body = b
|
||||
@@ -403,13 +399,13 @@ func runInteractiveConfig() error {
|
||||
|
||||
// Config Reloaded
|
||||
fmt.Println(" Config Reloaded notification:")
|
||||
fmt.Printf(" Title (current: %s): ", reloadTitle)
|
||||
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgConfigReloaded].Title)
|
||||
if scanner.Scan() {
|
||||
if t := strings.TrimSpace(scanner.Text()); t != "" {
|
||||
cfg.Notifications.Messages.ConfigReloaded.Title = t
|
||||
}
|
||||
}
|
||||
fmt.Printf(" Body (current: %s): ", reloadBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgConfigReloaded].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.ConfigReloaded.Body = b
|
||||
@@ -419,13 +415,13 @@ func runInteractiveConfig() error {
|
||||
|
||||
// Operation Cancelled
|
||||
fmt.Println(" Operation Cancelled notification:")
|
||||
fmt.Printf(" Title (current: %s): ", cancelTitle)
|
||||
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgOperationCancelled].Title)
|
||||
if scanner.Scan() {
|
||||
if t := strings.TrimSpace(scanner.Text()); t != "" {
|
||||
cfg.Notifications.Messages.OperationCancelled.Title = t
|
||||
}
|
||||
}
|
||||
fmt.Printf(" Body (current: %s): ", cancelBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgOperationCancelled].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.OperationCancelled.Body = b
|
||||
@@ -435,7 +431,7 @@ func runInteractiveConfig() error {
|
||||
|
||||
// Recording Aborted (body only)
|
||||
fmt.Println(" Recording Aborted notification:")
|
||||
fmt.Printf(" Body (current: %s): ", abortRecBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgRecordingAborted].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.RecordingAborted.Body = b
|
||||
@@ -445,7 +441,7 @@ func runInteractiveConfig() error {
|
||||
|
||||
// Injection Aborted (body only)
|
||||
fmt.Println(" Injection Aborted notification:")
|
||||
fmt.Printf(" Body (current: %s): ", abortInjBody)
|
||||
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgInjectionAborted].Body)
|
||||
if scanner.Scan() {
|
||||
if b := strings.TrimSpace(scanner.Text()); b != "" {
|
||||
cfg.Notifications.Messages.InjectionAborted.Body = b
|
||||
|
||||
+25
-39
@@ -5,10 +5,12 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/injection"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/notify"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/recording"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/transcriber"
|
||||
)
|
||||
@@ -64,52 +66,36 @@ type MessagesConfig struct {
|
||||
InjectionAborted MessageConfig `toml:"injection_aborted"`
|
||||
}
|
||||
|
||||
func (c *Config) GetRecordingStarted() (title, body string) {
|
||||
m := c.Notifications.Messages.RecordingStarted
|
||||
if m.Title == "" && m.Body == "" {
|
||||
return "Hyprvoice", "Recording Started"
|
||||
}
|
||||
return m.Title, m.Body
|
||||
}
|
||||
// Resolve merges user config with defaults from MessageDefs
|
||||
func (m *MessagesConfig) Resolve() map[notify.MessageType]notify.Message {
|
||||
result := make(map[notify.MessageType]notify.Message)
|
||||
|
||||
func (c *Config) GetTranscribing() (title, body string) {
|
||||
m := c.Notifications.Messages.Transcribing
|
||||
if m.Title == "" && m.Body == "" {
|
||||
return "Hyprvoice", "Recording Ended... Transcribing"
|
||||
// Build toml tag → field index map
|
||||
v := reflect.ValueOf(m).Elem()
|
||||
t := v.Type()
|
||||
tagToField := make(map[string]int)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
tagToField[t.Field(i).Tag.Get("toml")] = i
|
||||
}
|
||||
return m.Title, m.Body
|
||||
}
|
||||
|
||||
func (c *Config) GetConfigReloaded() (title, body string) {
|
||||
m := c.Notifications.Messages.ConfigReloaded
|
||||
if m.Title == "" && m.Body == "" {
|
||||
return "Hyprvoice", "Config Reloaded"
|
||||
for _, def := range notify.MessageDefs {
|
||||
msg := notify.Message{
|
||||
Title: def.DefaultTitle,
|
||||
Body: def.DefaultBody,
|
||||
IsError: def.IsError,
|
||||
}
|
||||
return m.Title, m.Body
|
||||
}
|
||||
|
||||
func (c *Config) GetOperationCancelled() (title, body string) {
|
||||
m := c.Notifications.Messages.OperationCancelled
|
||||
if m.Title == "" && m.Body == "" {
|
||||
return "Hyprvoice", "Operation Cancelled"
|
||||
if idx, ok := tagToField[def.ConfigKey]; ok {
|
||||
userMsg := v.Field(idx).Interface().(MessageConfig)
|
||||
if userMsg.Title != "" {
|
||||
msg.Title = userMsg.Title
|
||||
}
|
||||
return m.Title, m.Body
|
||||
}
|
||||
|
||||
func (c *Config) GetRecordingAborted() string {
|
||||
m := c.Notifications.Messages.RecordingAborted
|
||||
if m.Body == "" {
|
||||
return "Recording Aborted"
|
||||
if userMsg.Body != "" {
|
||||
msg.Body = userMsg.Body
|
||||
}
|
||||
return m.Body
|
||||
}
|
||||
|
||||
func (c *Config) GetInjectionAborted() string {
|
||||
m := c.Notifications.Messages.InjectionAborted
|
||||
if m.Body == "" {
|
||||
return "Injection Aborted"
|
||||
}
|
||||
return m.Body
|
||||
result[def.Type] = msg
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Config) ToRecordingConfig() recording.Config {
|
||||
|
||||
+32
-120
@@ -5,6 +5,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/leonardotrapani/hyprvoice/internal/notify"
|
||||
)
|
||||
|
||||
// createTestConfig returns a valid configuration for testing
|
||||
@@ -1229,142 +1231,52 @@ func TestConfig_Validate_GroqTranslation_RejectsTurbo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_MessageGetters_Defaults(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
func TestMessagesConfig_Resolve_Defaults(t *testing.T) {
|
||||
cfg := createTestConfig()
|
||||
msgs := cfg.Notifications.Messages.Resolve()
|
||||
|
||||
t.Run("GetRecordingStarted returns defaults", func(t *testing.T) {
|
||||
title, body := config.GetRecordingStarted()
|
||||
if title != "Hyprvoice" {
|
||||
t.Errorf("GetRecordingStarted() title = %q, want %q", title, "Hyprvoice")
|
||||
// Check defaults are applied
|
||||
if msgs[notify.MsgRecordingStarted].Title != "Hyprvoice" {
|
||||
t.Errorf("MsgRecordingStarted title = %q, want %q", msgs[notify.MsgRecordingStarted].Title, "Hyprvoice")
|
||||
}
|
||||
if body != "Recording Started" {
|
||||
t.Errorf("GetRecordingStarted() body = %q, want %q", body, "Recording Started")
|
||||
if msgs[notify.MsgRecordingStarted].Body != "Recording Started" {
|
||||
t.Errorf("MsgRecordingStarted body = %q, want %q", msgs[notify.MsgRecordingStarted].Body, "Recording Started")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetTranscribing returns defaults", func(t *testing.T) {
|
||||
title, body := config.GetTranscribing()
|
||||
if title != "Hyprvoice" {
|
||||
t.Errorf("GetTranscribing() title = %q, want %q", title, "Hyprvoice")
|
||||
if msgs[notify.MsgTranscribing].Body != "Recording Ended... Transcribing" {
|
||||
t.Errorf("MsgTranscribing body = %q, want %q", msgs[notify.MsgTranscribing].Body, "Recording Ended... Transcribing")
|
||||
}
|
||||
if body != "Recording Ended... Transcribing" {
|
||||
t.Errorf("GetTranscribing() body = %q, want %q", body, "Recording Ended... Transcribing")
|
||||
if msgs[notify.MsgRecordingAborted].IsError != true {
|
||||
t.Errorf("MsgRecordingAborted IsError = %v, want true", msgs[notify.MsgRecordingAborted].IsError)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetConfigReloaded returns defaults", func(t *testing.T) {
|
||||
title, body := config.GetConfigReloaded()
|
||||
if title != "Hyprvoice" {
|
||||
t.Errorf("GetConfigReloaded() title = %q, want %q", title, "Hyprvoice")
|
||||
}
|
||||
if body != "Config Reloaded" {
|
||||
t.Errorf("GetConfigReloaded() body = %q, want %q", body, "Config Reloaded")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetOperationCancelled returns defaults", func(t *testing.T) {
|
||||
title, body := config.GetOperationCancelled()
|
||||
if title != "Hyprvoice" {
|
||||
t.Errorf("GetOperationCancelled() title = %q, want %q", title, "Hyprvoice")
|
||||
}
|
||||
if body != "Operation Cancelled" {
|
||||
t.Errorf("GetOperationCancelled() body = %q, want %q", body, "Operation Cancelled")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetRecordingAborted returns default", func(t *testing.T) {
|
||||
body := config.GetRecordingAborted()
|
||||
if body != "Recording Aborted" {
|
||||
t.Errorf("GetRecordingAborted() = %q, want %q", body, "Recording Aborted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetInjectionAborted returns default", func(t *testing.T) {
|
||||
body := config.GetInjectionAborted()
|
||||
if body != "Injection Aborted" {
|
||||
t.Errorf("GetInjectionAborted() = %q, want %q", body, "Injection Aborted")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig_MessageGetters_Custom(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.Notifications.Messages = MessagesConfig{
|
||||
func TestMessagesConfig_Resolve_CustomOverrides(t *testing.T) {
|
||||
cfg := createTestConfig()
|
||||
cfg.Notifications.Messages = MessagesConfig{
|
||||
RecordingStarted: MessageConfig{
|
||||
Title: "",
|
||||
Body: "🎤",
|
||||
},
|
||||
Transcribing: MessageConfig{
|
||||
Title: "",
|
||||
Body: "⏳",
|
||||
},
|
||||
ConfigReloaded: MessageConfig{
|
||||
Title: "",
|
||||
Body: "🔧",
|
||||
},
|
||||
OperationCancelled: MessageConfig{
|
||||
Title: "Custom",
|
||||
Body: "Cancelled!",
|
||||
Title: "Custom Title",
|
||||
Body: "Custom Body",
|
||||
},
|
||||
RecordingAborted: MessageConfig{
|
||||
Body: "Recording stopped",
|
||||
},
|
||||
InjectionAborted: MessageConfig{
|
||||
Body: "Inject failed",
|
||||
Body: "Custom Abort",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("GetRecordingStarted returns custom emoji", func(t *testing.T) {
|
||||
title, body := config.GetRecordingStarted()
|
||||
if title != "" {
|
||||
t.Errorf("GetRecordingStarted() title = %q, want %q", title, "")
|
||||
}
|
||||
if body != "🎤" {
|
||||
t.Errorf("GetRecordingStarted() body = %q, want %q", body, "🎤")
|
||||
}
|
||||
})
|
||||
msgs := cfg.Notifications.Messages.Resolve()
|
||||
|
||||
t.Run("GetTranscribing returns custom emoji", func(t *testing.T) {
|
||||
title, body := config.GetTranscribing()
|
||||
if title != "" {
|
||||
t.Errorf("GetTranscribing() title = %q, want %q", title, "")
|
||||
// Custom values should override defaults
|
||||
if msgs[notify.MsgRecordingStarted].Title != "Custom Title" {
|
||||
t.Errorf("MsgRecordingStarted title = %q, want %q", msgs[notify.MsgRecordingStarted].Title, "Custom Title")
|
||||
}
|
||||
if body != "⏳" {
|
||||
t.Errorf("GetTranscribing() body = %q, want %q", body, "⏳")
|
||||
if msgs[notify.MsgRecordingStarted].Body != "Custom Body" {
|
||||
t.Errorf("MsgRecordingStarted body = %q, want %q", msgs[notify.MsgRecordingStarted].Body, "Custom Body")
|
||||
}
|
||||
if msgs[notify.MsgRecordingAborted].Body != "Custom Abort" {
|
||||
t.Errorf("MsgRecordingAborted body = %q, want %q", msgs[notify.MsgRecordingAborted].Body, "Custom Abort")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetConfigReloaded returns custom emoji", func(t *testing.T) {
|
||||
title, body := config.GetConfigReloaded()
|
||||
if title != "" {
|
||||
t.Errorf("GetConfigReloaded() title = %q, want %q", title, "")
|
||||
// Non-customized messages should still have defaults
|
||||
if msgs[notify.MsgTranscribing].Title != "Hyprvoice" {
|
||||
t.Errorf("MsgTranscribing title = %q, want %q", msgs[notify.MsgTranscribing].Title, "Hyprvoice")
|
||||
}
|
||||
if body != "🔧" {
|
||||
t.Errorf("GetConfigReloaded() body = %q, want %q", body, "🔧")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetOperationCancelled returns custom values", func(t *testing.T) {
|
||||
title, body := config.GetOperationCancelled()
|
||||
if title != "Custom" {
|
||||
t.Errorf("GetOperationCancelled() title = %q, want %q", title, "Custom")
|
||||
}
|
||||
if body != "Cancelled!" {
|
||||
t.Errorf("GetOperationCancelled() body = %q, want %q", body, "Cancelled!")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetRecordingAborted returns custom value", func(t *testing.T) {
|
||||
body := config.GetRecordingAborted()
|
||||
if body != "Recording stopped" {
|
||||
t.Errorf("GetRecordingAborted() = %q, want %q", body, "Recording stopped")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetInjectionAborted returns custom value", func(t *testing.T) {
|
||||
body := config.GetInjectionAborted()
|
||||
if body != "Inject failed" {
|
||||
t.Errorf("GetInjectionAborted() = %q, want %q", body, "Inject failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+10
-17
@@ -32,19 +32,15 @@ type Daemon struct {
|
||||
|
||||
func New() (*Daemon, error) {
|
||||
configMgr, err := config.NewManager()
|
||||
|
||||
conf := configMgr.GetConfig()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config manager: %w", err)
|
||||
}
|
||||
|
||||
conf := configMgr.GetConfig()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
n := notify.GetNotifierBasedOnConfig(conf)
|
||||
|
||||
d := &Daemon{
|
||||
notifier: n,
|
||||
notifier: notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve()),
|
||||
configMgr: configMgr,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
@@ -58,12 +54,12 @@ func (d *Daemon) onConfigReload() {
|
||||
d.stopPipeline()
|
||||
|
||||
conf := d.configMgr.GetConfig()
|
||||
title, body := conf.GetConfigReloaded()
|
||||
d.notifier.Notify(title, body)
|
||||
|
||||
d.mu.Lock()
|
||||
d.notifier = notify.GetNotifierBasedOnConfig(conf)
|
||||
d.notifier = notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve())
|
||||
d.mu.Unlock()
|
||||
|
||||
d.notifier.Send(notify.MsgConfigReloaded)
|
||||
}
|
||||
|
||||
func (d *Daemon) status() pipeline.Status {
|
||||
@@ -192,13 +188,12 @@ func (d *Daemon) toggle() {
|
||||
d.pipeline = p
|
||||
d.mu.Unlock()
|
||||
|
||||
title, body := conf.GetRecordingStarted()
|
||||
go d.notifier.Notify(title, body)
|
||||
go d.notifier.Send(notify.MsgRecordingStarted)
|
||||
go d.monitorPipelineErrors(p)
|
||||
|
||||
case pipeline.Recording:
|
||||
d.stopPipeline()
|
||||
go d.notifier.Error(conf.GetRecordingAborted())
|
||||
go d.notifier.Send(notify.MsgRecordingAborted)
|
||||
|
||||
case pipeline.Transcribing:
|
||||
d.mu.RLock()
|
||||
@@ -210,12 +205,11 @@ func (d *Daemon) toggle() {
|
||||
} else {
|
||||
d.mu.RUnlock()
|
||||
}
|
||||
title, body := conf.GetTranscribing()
|
||||
go d.notifier.Notify(title, body)
|
||||
go d.notifier.Send(notify.MsgTranscribing)
|
||||
|
||||
case pipeline.Injecting:
|
||||
d.stopPipeline()
|
||||
go d.notifier.Error(conf.GetInjectionAborted())
|
||||
go d.notifier.Send(notify.MsgInjectionAborted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +219,7 @@ func (d *Daemon) cancelPipeline() {
|
||||
log.Printf("Daemon: Cancel requested but pipeline is idle, ignoring")
|
||||
default:
|
||||
d.stopPipeline()
|
||||
title, body := d.configMgr.GetConfig().GetOperationCancelled()
|
||||
go d.notifier.Notify(title, body)
|
||||
go d.notifier.Send(notify.MsgOperationCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package notify
|
||||
|
||||
// MessageType identifies a notification event
|
||||
type MessageType int
|
||||
|
||||
const (
|
||||
MsgRecordingStarted MessageType = iota
|
||||
MsgTranscribing
|
||||
MsgConfigReloaded
|
||||
MsgOperationCancelled
|
||||
MsgRecordingAborted
|
||||
MsgInjectionAborted
|
||||
)
|
||||
|
||||
// MessageDef defines a message type with its config key and defaults
|
||||
type MessageDef struct {
|
||||
Type MessageType
|
||||
ConfigKey string // TOML key under [notifications.messages]
|
||||
DefaultTitle string
|
||||
DefaultBody string
|
||||
IsError bool // error notifications use critical urgency, no custom title
|
||||
}
|
||||
|
||||
// MessageDefs is the single source of truth for all notification messages
|
||||
var MessageDefs = []MessageDef{
|
||||
{MsgRecordingStarted, "recording_started", "Hyprvoice", "Recording Started", false},
|
||||
{MsgTranscribing, "transcribing", "Hyprvoice", "Recording Ended... Transcribing", false},
|
||||
{MsgConfigReloaded, "config_reloaded", "Hyprvoice", "Config Reloaded", false},
|
||||
{MsgOperationCancelled, "operation_cancelled", "Hyprvoice", "Operation Cancelled", false},
|
||||
{MsgRecordingAborted, "recording_aborted", "", "Recording Aborted", true},
|
||||
{MsgInjectionAborted, "injection_aborted", "", "Injection Aborted", true},
|
||||
}
|
||||
|
||||
// Message is a resolved message ready for display
|
||||
type Message struct {
|
||||
Title string
|
||||
Body string
|
||||
IsError bool
|
||||
}
|
||||
+54
-31
@@ -1,63 +1,86 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type Notifier interface {
|
||||
Error(msg string)
|
||||
Notify(title, message string)
|
||||
Send(mt MessageType)
|
||||
Error(msg string) // for dynamic errors (e.g., pipeline errors)
|
||||
}
|
||||
|
||||
type Desktop struct{}
|
||||
|
||||
func (d Desktop) RecordingStarted() {
|
||||
d.Notify("Hyprvoice", "Recording Started")
|
||||
// NewNotifier creates a notifier based on type with resolved messages
|
||||
func NewNotifier(notifType string, messages map[MessageType]Message) Notifier {
|
||||
switch notifType {
|
||||
case "desktop":
|
||||
return NewDesktop(messages)
|
||||
case "log":
|
||||
return NewLog(messages)
|
||||
default:
|
||||
return &Nop{}
|
||||
}
|
||||
}
|
||||
|
||||
func (d Desktop) Transcribing() {
|
||||
d.Notify("Hyprvoice", "Transcribing...")
|
||||
type Desktop struct {
|
||||
messages map[MessageType]Message
|
||||
}
|
||||
|
||||
func (Desktop) Error(msg string) {
|
||||
func NewDesktop(messages map[MessageType]Message) *Desktop {
|
||||
return &Desktop{messages: messages}
|
||||
}
|
||||
|
||||
func (d *Desktop) Send(mt MessageType) {
|
||||
msg, ok := d.messages[mt]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if msg.IsError {
|
||||
d.Error(msg.Body)
|
||||
return
|
||||
}
|
||||
d.notify(msg.Title, msg.Body)
|
||||
}
|
||||
|
||||
func (d *Desktop) Error(msg string) {
|
||||
cmd := exec.Command("notify-send", "-a", "Hyprvoice", "-u", "critical", "Hyprvoice Error", msg)
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Printf("Failed to send error notification: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (Desktop) Notify(title, message string) {
|
||||
cmd := exec.Command("notify-send", "-a", "Hyprvoice", title, message)
|
||||
func (d *Desktop) notify(title, body string) {
|
||||
cmd := exec.Command("notify-send", "-a", "Hyprvoice", title, body)
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Printf("Failed to send notification: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Log struct{}
|
||||
|
||||
func (l Log) Error(msg string) {
|
||||
l.Notify("Hyprvoice Error", msg)
|
||||
type Log struct {
|
||||
messages map[MessageType]Message
|
||||
}
|
||||
|
||||
func (Log) Notify(title, message string) {
|
||||
log.Printf("%s: %s", title, message)
|
||||
func NewLog(messages map[MessageType]Message) *Log {
|
||||
return &Log{messages: messages}
|
||||
}
|
||||
|
||||
func (l *Log) Send(mt MessageType) {
|
||||
msg, ok := l.messages[mt]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if msg.IsError {
|
||||
l.Error(msg.Body)
|
||||
return
|
||||
}
|
||||
log.Printf("%s: %s", msg.Title, msg.Body)
|
||||
}
|
||||
|
||||
func (l *Log) Error(msg string) {
|
||||
log.Printf("Hyprvoice Error: %s", msg)
|
||||
}
|
||||
|
||||
type Nop struct{}
|
||||
|
||||
func (Nop) Send(mt MessageType) {}
|
||||
func (Nop) Error(msg string) {}
|
||||
func (Nop) Notify(title, message string) {}
|
||||
|
||||
func GetNotifierBasedOnConfig(c *config.Config) Notifier {
|
||||
switch c.Notifications.Type {
|
||||
case "desktop":
|
||||
return Desktop{}
|
||||
case "log":
|
||||
return Log{}
|
||||
case "none":
|
||||
return Nop{}
|
||||
}
|
||||
return Nop{}
|
||||
}
|
||||
|
||||
+76
-157
@@ -2,202 +2,121 @@ package notify
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||
)
|
||||
|
||||
func TestDesktop_Notify(t *testing.T) {
|
||||
desktop := Desktop{}
|
||||
|
||||
// Test normal notification
|
||||
desktop.Notify("Test Title", "Test Message")
|
||||
|
||||
// Test error notification
|
||||
desktop.Error("Test Error Message")
|
||||
|
||||
// Test specific methods
|
||||
desktop.RecordingStarted()
|
||||
desktop.Transcribing()
|
||||
func testMessages() map[MessageType]Message {
|
||||
return map[MessageType]Message{
|
||||
MsgRecordingStarted: {Title: "Hyprvoice", Body: "Recording Started", IsError: false},
|
||||
MsgTranscribing: {Title: "Hyprvoice", Body: "Transcribing", IsError: false},
|
||||
MsgConfigReloaded: {Title: "Hyprvoice", Body: "Config Reloaded", IsError: false},
|
||||
MsgOperationCancelled: {Title: "Hyprvoice", Body: "Operation Cancelled", IsError: false},
|
||||
MsgRecordingAborted: {Title: "", Body: "Recording Aborted", IsError: true},
|
||||
MsgInjectionAborted: {Title: "", Body: "Injection Aborted", IsError: true},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLog_Notify(t *testing.T) {
|
||||
logNotifier := Log{}
|
||||
func TestDesktop_Send(t *testing.T) {
|
||||
desktop := NewDesktop(testMessages())
|
||||
|
||||
// Test normal notification
|
||||
logNotifier.Notify("Test Title", "Test Message")
|
||||
// Test Send for different message types (won't actually send, just verify no panic)
|
||||
desktop.Send(MsgRecordingStarted)
|
||||
desktop.Send(MsgTranscribing)
|
||||
desktop.Send(MsgRecordingAborted) // error type
|
||||
}
|
||||
|
||||
// Test error notification
|
||||
func TestDesktop_Error(t *testing.T) {
|
||||
desktop := NewDesktop(testMessages())
|
||||
desktop.Error("Test Error Message")
|
||||
}
|
||||
|
||||
func TestLog_Send(t *testing.T) {
|
||||
logNotifier := NewLog(testMessages())
|
||||
|
||||
logNotifier.Send(MsgRecordingStarted)
|
||||
logNotifier.Send(MsgRecordingAborted) // error type
|
||||
}
|
||||
|
||||
func TestLog_Error(t *testing.T) {
|
||||
logNotifier := NewLog(testMessages())
|
||||
logNotifier.Error("Test Error Message")
|
||||
}
|
||||
|
||||
func TestNop_Notify(t *testing.T) {
|
||||
func TestNop_Send(t *testing.T) {
|
||||
nop := Nop{}
|
||||
nop.Send(MsgRecordingStarted)
|
||||
nop.Send(MsgRecordingAborted)
|
||||
}
|
||||
|
||||
// Test that these methods don't panic
|
||||
nop.Notify("Test Title", "Test Message")
|
||||
func TestNop_Error(t *testing.T) {
|
||||
nop := Nop{}
|
||||
nop.Error("Test Error Message")
|
||||
}
|
||||
|
||||
func TestGetNotifierBasedOnConfig(t *testing.T) {
|
||||
func TestNewNotifier(t *testing.T) {
|
||||
msgs := testMessages()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *config.Config
|
||||
expected string
|
||||
notifType string
|
||||
expectType string
|
||||
}{
|
||||
{
|
||||
name: "desktop notification type",
|
||||
config: &config.Config{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "desktop",
|
||||
},
|
||||
},
|
||||
expected: "desktop",
|
||||
},
|
||||
{
|
||||
name: "log notification type",
|
||||
config: &config.Config{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "log",
|
||||
},
|
||||
},
|
||||
expected: "log",
|
||||
},
|
||||
{
|
||||
name: "none notification type",
|
||||
config: &config.Config{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "none",
|
||||
},
|
||||
},
|
||||
expected: "nop",
|
||||
},
|
||||
{
|
||||
name: "unknown notification type",
|
||||
config: &config.Config{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "unknown",
|
||||
},
|
||||
},
|
||||
expected: "nop",
|
||||
},
|
||||
{"desktop", "desktop", "*notify.Desktop"},
|
||||
{"log", "log", "*notify.Log"},
|
||||
{"none", "none", "*notify.Nop"},
|
||||
{"unknown", "unknown", "*notify.Nop"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
notifier := GetNotifierBasedOnConfig(tt.config)
|
||||
notifier := NewNotifier(tt.notifType, msgs)
|
||||
|
||||
// Test the notifier by calling its methods
|
||||
notifier.Notify("Test", "Message")
|
||||
// Test the notifier works
|
||||
notifier.Send(MsgRecordingStarted)
|
||||
notifier.Error("Error")
|
||||
|
||||
// Check the type by testing behavior
|
||||
switch tt.expected {
|
||||
case "desktop":
|
||||
// Desktop notifier should not panic
|
||||
if desktop, ok := notifier.(Desktop); ok {
|
||||
desktop.RecordingStarted()
|
||||
desktop.Transcribing()
|
||||
}
|
||||
case "log":
|
||||
// Log notifier should not panic
|
||||
if logNotifier, ok := notifier.(Log); ok {
|
||||
logNotifier.Notify("Test", "Message")
|
||||
logNotifier.Error("Error")
|
||||
}
|
||||
case "nop":
|
||||
// Nop notifier should not panic
|
||||
if nop, ok := notifier.(Nop); ok {
|
||||
nop.Notify("Test", "Message")
|
||||
nop.Error("Error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesktop_Methods(t *testing.T) {
|
||||
desktop := Desktop{}
|
||||
|
||||
// Test RecordingStarted method
|
||||
desktop.RecordingStarted()
|
||||
|
||||
// Test Transcribing method
|
||||
desktop.Transcribing()
|
||||
|
||||
// Test Error method
|
||||
desktop.Error("Test error")
|
||||
|
||||
// Test Notify method
|
||||
desktop.Notify("Test Title", "Test Message")
|
||||
}
|
||||
|
||||
func TestLog_Methods(t *testing.T) {
|
||||
logNotifier := Log{}
|
||||
|
||||
// Test Error method
|
||||
logNotifier.Error("Test error")
|
||||
|
||||
// Test Notify method
|
||||
logNotifier.Notify("Test Title", "Test Message")
|
||||
}
|
||||
|
||||
func TestNop_Methods(t *testing.T) {
|
||||
nop := Nop{}
|
||||
|
||||
// Test Error method
|
||||
nop.Error("Test error")
|
||||
|
||||
// Test Notify method
|
||||
nop.Notify("Test Title", "Test Message")
|
||||
}
|
||||
|
||||
func TestNotifierInterface(t *testing.T) {
|
||||
msgs := testMessages()
|
||||
|
||||
// Test that all notifiers implement the Notifier interface
|
||||
var notifier Notifier
|
||||
|
||||
// Test Desktop
|
||||
notifier = Desktop{}
|
||||
notifier.Notify("Test", "Message")
|
||||
notifier = NewDesktop(msgs)
|
||||
notifier.Send(MsgRecordingStarted)
|
||||
notifier.Error("Error")
|
||||
|
||||
// Test Log
|
||||
notifier = Log{}
|
||||
notifier.Notify("Test", "Message")
|
||||
notifier = NewLog(msgs)
|
||||
notifier.Send(MsgRecordingStarted)
|
||||
notifier.Error("Error")
|
||||
|
||||
// Test Nop
|
||||
notifier = Nop{}
|
||||
notifier.Notify("Test", "Message")
|
||||
notifier = &Nop{}
|
||||
notifier.Send(MsgRecordingStarted)
|
||||
notifier.Error("Error")
|
||||
}
|
||||
|
||||
func TestNotificationTypes(t *testing.T) {
|
||||
// Test different notification configurations
|
||||
configs := []*config.Config{
|
||||
{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "desktop",
|
||||
},
|
||||
},
|
||||
{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "log",
|
||||
},
|
||||
},
|
||||
{
|
||||
Notifications: config.NotificationsConfig{
|
||||
Type: "none",
|
||||
},
|
||||
},
|
||||
func TestMessageDefs(t *testing.T) {
|
||||
// Verify MessageDefs contains expected entries
|
||||
if len(MessageDefs) != 6 {
|
||||
t.Errorf("Expected 6 MessageDefs, got %d", len(MessageDefs))
|
||||
}
|
||||
|
||||
for _, cfg := range configs {
|
||||
t.Run("type_"+cfg.Notifications.Type, func(t *testing.T) {
|
||||
notifier := GetNotifierBasedOnConfig(cfg)
|
||||
|
||||
// Test that the notifier works
|
||||
notifier.Notify("Test Title", "Test Message")
|
||||
notifier.Error("Test Error")
|
||||
})
|
||||
// Verify each has required fields
|
||||
for _, def := range MessageDefs {
|
||||
if def.ConfigKey == "" {
|
||||
t.Errorf("MessageDef type %d has empty ConfigKey", def.Type)
|
||||
}
|
||||
if def.DefaultBody == "" {
|
||||
t.Errorf("MessageDef type %d has empty DefaultBody", def.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSend_UnknownMessageType(t *testing.T) {
|
||||
msgs := testMessages()
|
||||
desktop := NewDesktop(msgs)
|
||||
|
||||
// Should not panic with unknown message type
|
||||
desktop.Send(MessageType(999))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user