internal refactor of notifications
This commit is contained in:
@@ -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
|
||||
}
|
||||
+55
-32
@@ -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) 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{}
|
||||
}
|
||||
func (Nop) Send(mt MessageType) {}
|
||||
func (Nop) Error(msg string) {}
|
||||
|
||||
+77
-158
@@ -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
|
||||
name 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