mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
switch hto monorepo structure
This commit is contained in:
340
backend/internal/server/cups/manager.go
Normal file
340
backend/internal/server/cups/manager.go
Normal file
@@ -0,0 +1,340 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/backend/pkg/ipp"
|
||||
)
|
||||
|
||||
func NewManager() (*Manager, error) {
|
||||
host := os.Getenv("DMS_IPP_HOST")
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
|
||||
portStr := os.Getenv("DMS_IPP_PORT")
|
||||
port := 631
|
||||
if portStr != "" {
|
||||
if p, err := strconv.Atoi(portStr); err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
|
||||
username := os.Getenv("DMS_IPP_USERNAME")
|
||||
password := os.Getenv("DMS_IPP_PASSWORD")
|
||||
|
||||
client := ipp.NewCUPSClient(host, port, username, password, false)
|
||||
baseURL := fmt.Sprintf("http://%s:%d", host, port)
|
||||
|
||||
m := &Manager{
|
||||
state: &CUPSState{
|
||||
Printers: make(map[string]*Printer),
|
||||
},
|
||||
client: client,
|
||||
baseURL: baseURL,
|
||||
stateMutex: sync.RWMutex{},
|
||||
stopChan: make(chan struct{}),
|
||||
dirty: make(chan struct{}, 1),
|
||||
subscribers: make(map[string]chan CUPSState),
|
||||
subMutex: sync.RWMutex{},
|
||||
}
|
||||
|
||||
if err := m.updateState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isLocalCUPS(host) {
|
||||
m.subscription = NewDBusSubscriptionManager(client, baseURL)
|
||||
log.Infof("[CUPS] Using D-Bus notifications for local CUPS")
|
||||
} else {
|
||||
m.subscription = NewSubscriptionManager(client, baseURL)
|
||||
log.Infof("[CUPS] Using IPPGET notifications for remote CUPS")
|
||||
}
|
||||
|
||||
m.notifierWg.Add(1)
|
||||
go m.notifier()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func isLocalCUPS(host string) bool {
|
||||
switch host {
|
||||
case "localhost", "127.0.0.1", "::1", "":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) eventHandler() {
|
||||
defer m.eventWG.Done()
|
||||
|
||||
if m.subscription == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
return
|
||||
case event, ok := <-m.subscription.Events():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Debugf("[CUPS] Received event: %s (printer: %s, job: %d)",
|
||||
event.EventName, event.PrinterName, event.JobID)
|
||||
|
||||
if err := m.updateState(); err != nil {
|
||||
log.Warnf("[CUPS] Failed to update state after event: %v", err)
|
||||
} else {
|
||||
m.notifySubscribers()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) updateState() error {
|
||||
printers, err := m.GetPrinters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printerMap := make(map[string]*Printer, len(printers))
|
||||
for _, printer := range printers {
|
||||
jobs, err := m.GetJobs(printer.Name, "not-completed")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer.Jobs = jobs
|
||||
printerMap[printer.Name] = &printer
|
||||
}
|
||||
|
||||
m.stateMutex.Lock()
|
||||
m.state.Printers = printerMap
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) notifier() {
|
||||
defer m.notifierWg.Done()
|
||||
const minGap = 100 * time.Millisecond
|
||||
timer := time.NewTimer(minGap)
|
||||
timer.Stop()
|
||||
var pending bool
|
||||
for {
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
timer.Stop()
|
||||
return
|
||||
case <-m.dirty:
|
||||
if pending {
|
||||
continue
|
||||
}
|
||||
pending = true
|
||||
timer.Reset(minGap)
|
||||
case <-timer.C:
|
||||
if !pending {
|
||||
continue
|
||||
}
|
||||
m.subMutex.RLock()
|
||||
if len(m.subscribers) == 0 {
|
||||
m.subMutex.RUnlock()
|
||||
pending = false
|
||||
continue
|
||||
}
|
||||
|
||||
currentState := m.snapshotState()
|
||||
|
||||
if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) {
|
||||
m.subMutex.RUnlock()
|
||||
pending = false
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ch := range m.subscribers {
|
||||
select {
|
||||
case ch <- currentState:
|
||||
default:
|
||||
}
|
||||
}
|
||||
m.subMutex.RUnlock()
|
||||
|
||||
stateCopy := currentState
|
||||
m.lastNotifiedState = &stateCopy
|
||||
pending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) notifySubscribers() {
|
||||
select {
|
||||
case m.dirty <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) GetState() CUPSState {
|
||||
return m.snapshotState()
|
||||
}
|
||||
|
||||
func (m *Manager) snapshotState() CUPSState {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
|
||||
s := CUPSState{
|
||||
Printers: make(map[string]*Printer, len(m.state.Printers)),
|
||||
}
|
||||
for name, printer := range m.state.Printers {
|
||||
printerCopy := *printer
|
||||
s.Printers[name] = &printerCopy
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||
ch := make(chan CUPSState, 64)
|
||||
m.subMutex.Lock()
|
||||
wasEmpty := len(m.subscribers) == 0
|
||||
m.subscribers[id] = ch
|
||||
m.subMutex.Unlock()
|
||||
|
||||
if wasEmpty && m.subscription != nil {
|
||||
if err := m.subscription.Start(); err != nil {
|
||||
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
||||
} else {
|
||||
m.eventWG.Add(1)
|
||||
go m.eventHandler()
|
||||
}
|
||||
}
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (m *Manager) Unsubscribe(id string) {
|
||||
m.subMutex.Lock()
|
||||
if ch, ok := m.subscribers[id]; ok {
|
||||
close(ch)
|
||||
delete(m.subscribers, id)
|
||||
}
|
||||
isEmpty := len(m.subscribers) == 0
|
||||
m.subMutex.Unlock()
|
||||
|
||||
if isEmpty && m.subscription != nil {
|
||||
m.subscription.Stop()
|
||||
m.eventWG.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Close() {
|
||||
close(m.stopChan)
|
||||
|
||||
if m.subscription != nil {
|
||||
m.subscription.Stop()
|
||||
}
|
||||
|
||||
m.eventWG.Wait()
|
||||
m.notifierWg.Wait()
|
||||
|
||||
m.subMutex.Lock()
|
||||
for _, ch := range m.subscribers {
|
||||
close(ch)
|
||||
}
|
||||
m.subscribers = make(map[string]chan CUPSState)
|
||||
m.subMutex.Unlock()
|
||||
}
|
||||
|
||||
func stateChanged(old, new *CUPSState) bool {
|
||||
if len(old.Printers) != len(new.Printers) {
|
||||
return true
|
||||
}
|
||||
for name, oldPrinter := range old.Printers {
|
||||
newPrinter, exists := new.Printers[name]
|
||||
if !exists {
|
||||
return true
|
||||
}
|
||||
if oldPrinter.State != newPrinter.State ||
|
||||
oldPrinter.StateReason != newPrinter.StateReason ||
|
||||
len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parsePrinterState(attrs ipp.Attributes) string {
|
||||
if stateAttr, ok := attrs[ipp.AttributePrinterState]; ok && len(stateAttr) > 0 {
|
||||
if state, ok := stateAttr[0].Value.(int); ok {
|
||||
switch state {
|
||||
case 3:
|
||||
return "idle"
|
||||
case 4:
|
||||
return "processing"
|
||||
case 5:
|
||||
return "stopped"
|
||||
default:
|
||||
return fmt.Sprintf("%d", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func parseJobState(attrs ipp.Attributes) string {
|
||||
if stateAttr, ok := attrs[ipp.AttributeJobState]; ok && len(stateAttr) > 0 {
|
||||
if state, ok := stateAttr[0].Value.(int); ok {
|
||||
switch state {
|
||||
case 3:
|
||||
return "pending"
|
||||
case 4:
|
||||
return "pending-held"
|
||||
case 5:
|
||||
return "processing"
|
||||
case 6:
|
||||
return "processing-stopped"
|
||||
case 7:
|
||||
return "canceled"
|
||||
case 8:
|
||||
return "aborted"
|
||||
case 9:
|
||||
return "completed"
|
||||
default:
|
||||
return fmt.Sprintf("%d", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func getStringAttr(attrs ipp.Attributes, key string) string {
|
||||
if attr, ok := attrs[key]; ok && len(attr) > 0 {
|
||||
if val, ok := attr[0].Value.(string); ok {
|
||||
return val
|
||||
}
|
||||
return fmt.Sprintf("%v", attr[0].Value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getIntAttr(attrs ipp.Attributes, key string) int {
|
||||
if attr, ok := attrs[key]; ok && len(attr) > 0 {
|
||||
if val, ok := attr[0].Value.(int); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getBoolAttr(attrs ipp.Attributes, key string) bool {
|
||||
if attr, ok := attrs[key]; ok && len(attr) > 0 {
|
||||
if val, ok := attr[0].Value.(bool); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user