1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/core/internal/server/cups/subscription_dbus.go
2025-11-12 23:12:31 -05:00

296 lines
7.1 KiB
Go

package cups
import (
"fmt"
"strings"
"sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
"github.com/godbus/dbus/v5"
)
type DBusSubscriptionManager struct {
client CUPSClientInterface
subscriptionID int
eventChan chan SubscriptionEvent
stopChan chan struct{}
wg sync.WaitGroup
baseURL string
running bool
mu sync.Mutex
conn *dbus.Conn
}
func NewDBusSubscriptionManager(client CUPSClientInterface, baseURL string) *DBusSubscriptionManager {
return &DBusSubscriptionManager{
client: client,
eventChan: make(chan SubscriptionEvent, 100),
stopChan: make(chan struct{}),
baseURL: baseURL,
}
}
func (sm *DBusSubscriptionManager) Start() error {
sm.mu.Lock()
if sm.running {
sm.mu.Unlock()
return fmt.Errorf("subscription manager already running")
}
sm.running = true
sm.mu.Unlock()
conn, err := dbus.ConnectSystemBus()
if err != nil {
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return fmt.Errorf("connect to system bus: %w", err)
}
sm.conn = conn
subID, err := sm.createDBusSubscription()
if err != nil {
sm.conn.Close()
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return fmt.Errorf("failed to create D-Bus subscription: %w", err)
}
sm.subscriptionID = subID
log.Infof("[CUPS] Created D-Bus subscription with ID %d", subID)
if err := sm.conn.AddMatchSignal(
dbus.WithMatchInterface("org.cups.cupsd.Notifier"),
); err != nil {
sm.cancelSubscription()
sm.conn.Close()
sm.mu.Lock()
sm.running = false
sm.mu.Unlock()
return fmt.Errorf("failed to add D-Bus match: %w", err)
}
sm.wg.Add(1)
go sm.dbusListenerLoop()
return nil
}
func (sm *DBusSubscriptionManager) createDBusSubscription() (int, error) {
req := ipp.NewRequest(ipp.OperationCreatePrinterSubscriptions, 2)
req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
req.SubscriptionAttributes = map[string]interface{}{
"notify-events": []string{
"printer-state-changed",
"printer-added",
"printer-deleted",
"job-created",
"job-completed",
"job-state-changed",
},
"notify-recipient-uri": "dbus:/",
"notify-lease-duration": 86400,
}
resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
if err != nil {
return 0, fmt.Errorf("SendRequest failed: %w", err)
}
if err := resp.CheckForErrors(); err != nil {
return 0, fmt.Errorf("IPP error: %w", err)
}
if len(resp.SubscriptionAttributes) > 0 {
if idAttr, ok := resp.SubscriptionAttributes[0]["notify-subscription-id"]; ok && len(idAttr) > 0 {
if val, ok := idAttr[0].Value.(int); ok {
return val, nil
}
}
}
return 0, fmt.Errorf("no subscription ID returned")
}
func (sm *DBusSubscriptionManager) dbusListenerLoop() {
defer sm.wg.Done()
signalChan := make(chan *dbus.Signal, 10)
sm.conn.Signal(signalChan)
defer sm.conn.RemoveSignal(signalChan)
for {
select {
case <-sm.stopChan:
return
case sig := <-signalChan:
if sig == nil {
continue
}
event := sm.parseDBusSignal(sig)
if event.EventName == "" {
continue
}
select {
case sm.eventChan <- event:
case <-sm.stopChan:
return
default:
log.Warn("[CUPS] Event channel full, dropping event")
}
}
}
}
func (sm *DBusSubscriptionManager) parseDBusSignal(sig *dbus.Signal) SubscriptionEvent {
event := SubscriptionEvent{}
switch sig.Name {
case "org.cups.cupsd.Notifier.JobStateChanged":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "job-state-changed"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
event.PrinterName = printerURI[idx+1:]
}
}
if jobID, ok := sig.Body[3].(uint32); ok {
event.JobID = int(jobID)
}
}
case "org.cups.cupsd.Notifier.JobCreated":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "job-created"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
event.PrinterName = printerURI[idx+1:]
}
}
if jobID, ok := sig.Body[3].(uint32); ok {
event.JobID = int(jobID)
}
}
case "org.cups.cupsd.Notifier.JobCompleted":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "job-completed"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
event.PrinterName = printerURI[idx+1:]
}
}
if jobID, ok := sig.Body[3].(uint32); ok {
event.JobID = int(jobID)
}
}
case "org.cups.cupsd.Notifier.PrinterStateChanged":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "printer-state-changed"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
if printerURI, ok := sig.Body[1].(string); ok && event.PrinterName == "" {
if idx := strings.LastIndex(printerURI, "/"); idx != -1 {
event.PrinterName = printerURI[idx+1:]
}
}
}
case "org.cups.cupsd.Notifier.PrinterAdded":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "printer-added"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
}
case "org.cups.cupsd.Notifier.PrinterDeleted":
if len(sig.Body) >= 6 {
if text, ok := sig.Body[0].(string); ok {
event.EventName = "printer-deleted"
parts := strings.Split(text, " ")
if len(parts) >= 2 {
event.PrinterName = parts[0]
}
}
}
}
return event
}
func (sm *DBusSubscriptionManager) Events() <-chan SubscriptionEvent {
return sm.eventChan
}
func (sm *DBusSubscriptionManager) Stop() {
sm.mu.Lock()
if !sm.running {
sm.mu.Unlock()
return
}
sm.running = false
sm.mu.Unlock()
close(sm.stopChan)
sm.wg.Wait()
if sm.subscriptionID != 0 {
sm.cancelSubscription()
sm.subscriptionID = 0
}
if sm.conn != nil {
sm.conn.Close()
sm.conn = nil
}
sm.stopChan = make(chan struct{})
}
func (sm *DBusSubscriptionManager) cancelSubscription() {
req := ipp.NewRequest(ipp.OperationCancelSubscription, 1)
req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL)
req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms"
req.OperationAttributes["notify-subscription-id"] = sm.subscriptionID
_, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil)
if err != nil {
log.Warnf("[CUPS] Failed to cancel subscription %d: %v", sm.subscriptionID, err)
} else {
log.Infof("[CUPS] Cancelled subscription %d", sm.subscriptionID)
}
}