225 lines
4.6 KiB
Go
225 lines
4.6 KiB
Go
package bus
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"syscall"
|
|
)
|
|
|
|
const (
|
|
SockName = "control.sock"
|
|
PidName = "hyprvoice.pid"
|
|
ProtoVer = "0.1"
|
|
)
|
|
|
|
type pidManager struct {
|
|
path string
|
|
}
|
|
|
|
func newPidManager() (*pidManager, error) {
|
|
pidPath, err := getPidPath()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get PID path: %w", err)
|
|
}
|
|
return &pidManager{path: pidPath}, nil
|
|
}
|
|
|
|
func (pm *pidManager) checkExisting() error {
|
|
log.Printf("Checking for existing daemon at: %s", pm.path)
|
|
|
|
pidData, err := os.ReadFile(pm.path)
|
|
if os.IsNotExist(err) {
|
|
log.Printf("No PID file found, daemon not running")
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("error reading PID file: %w", err)
|
|
}
|
|
|
|
log.Printf("Found PID file with content: %s", string(pidData))
|
|
|
|
pid, err := strconv.Atoi(string(pidData))
|
|
if err != nil {
|
|
log.Printf("Invalid PID in file, removing stale PID file: %v", err)
|
|
pm.removeStaleFile()
|
|
return nil
|
|
}
|
|
|
|
if pm.isProcessAlive(pid) {
|
|
log.Printf("Process %d is alive, daemon already running", pid)
|
|
return fmt.Errorf("daemon already running with PID %d", pid)
|
|
}
|
|
|
|
log.Printf("Process %d not alive, removing stale PID file", pid)
|
|
pm.removeStaleFile()
|
|
return nil
|
|
}
|
|
|
|
func (pm *pidManager) create() error {
|
|
if err := os.MkdirAll(filepath.Dir(pm.path), 0o700); err != nil {
|
|
return fmt.Errorf("failed to create PID directory: %w", err)
|
|
}
|
|
|
|
pid := os.Getpid()
|
|
log.Printf("Creating PID file at %s with PID %d", pm.path, pid)
|
|
|
|
err := os.WriteFile(pm.path, []byte(strconv.Itoa(pid)), 0o600)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write PID file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pm *pidManager) remove() error {
|
|
log.Printf("Removing PID file: %s", pm.path)
|
|
if err := os.Remove(pm.path); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to remove PID file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pm *pidManager) isProcessAlive(pid int) bool {
|
|
log.Printf("Checking if process %d is alive", pid)
|
|
|
|
proc, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
log.Printf("Process %d not found: %v", pid, err)
|
|
return false
|
|
}
|
|
|
|
err = proc.Signal(syscall.Signal(0))
|
|
if err != nil {
|
|
log.Printf("Process %d not alive (signal failed: %v)", pid, err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (pm *pidManager) removeStaleFile() {
|
|
if err := os.Remove(pm.path); err != nil && !os.IsNotExist(err) {
|
|
log.Printf("Warning: failed to remove stale PID file: %v", err)
|
|
}
|
|
}
|
|
|
|
type socketManager struct {
|
|
path string
|
|
}
|
|
|
|
func newSocketManager() (*socketManager, error) {
|
|
sockPath, err := getSockPath()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get socket path: %w", err)
|
|
}
|
|
return &socketManager{path: sockPath}, nil
|
|
}
|
|
|
|
func (sm *socketManager) listen() (net.Listener, error) {
|
|
if err := os.MkdirAll(filepath.Dir(sm.path), 0o700); err != nil {
|
|
return nil, fmt.Errorf("failed to create socket directory: %w", err)
|
|
}
|
|
|
|
os.Remove(sm.path)
|
|
|
|
listener, err := net.Listen("unix", sm.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to listen on socket %s: %w", sm.path, err)
|
|
}
|
|
|
|
return listener, nil
|
|
}
|
|
|
|
func (sm *socketManager) dial() (net.Conn, error) {
|
|
conn, err := net.Dial("unix", sm.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to dial socket %s: %w", sm.path, err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
func getSockPath() (string, error) {
|
|
dir, err := os.UserCacheDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(dir, "hyprvoice", SockName), nil
|
|
}
|
|
|
|
func getPidPath() (string, error) {
|
|
dir, err := os.UserCacheDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(dir, "hyprvoice", PidName), nil
|
|
}
|
|
|
|
func SockPath() (string, error) {
|
|
return getSockPath()
|
|
}
|
|
|
|
func Listen() (net.Listener, error) {
|
|
sm, err := newSocketManager()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sm.listen()
|
|
}
|
|
|
|
func Dial() (net.Conn, error) {
|
|
sm, err := newSocketManager()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sm.dial()
|
|
}
|
|
|
|
func CheckExistingDaemon() error {
|
|
pm, err := newPidManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pm.checkExisting()
|
|
}
|
|
|
|
func CreatePidFile() error {
|
|
pm, err := newPidManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pm.create()
|
|
}
|
|
|
|
func RemovePidFile() error {
|
|
pm, err := newPidManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pm.remove()
|
|
}
|
|
|
|
func SendCommand(cmd byte) (string, error) {
|
|
c, err := Dial()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to connect to daemon: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
_, err = c.Write([]byte{cmd, '\n'})
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to send command: %w", err)
|
|
}
|
|
|
|
resp, err := bufio.NewReader(c).ReadString('\n')
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|