diff options
| author | Nippy <nippy@rp1.hu> | 2026-05-09 13:33:09 +0200 |
|---|---|---|
| committer | Nippy <nippy@rp1.hu> | 2026-05-09 13:33:09 +0200 |
| commit | a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch) | |
| tree | 1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups | |
| parent | 34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff) | |
| download | RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip | |
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups')
15 files changed, 0 insertions, 4652 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go deleted file mode 100644 index 2f9b98b..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions.go +++ /dev/null @@ -1,479 +0,0 @@ -package cups - -import ( - "errors" - "fmt" - "net" - "net/url" - "os/exec" - "strings" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/config" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" -) - -func isAuthError(err error) bool { - if err == nil { - return false - } - - var httpErr ipp.HTTPError - if errors.As(err, &httpErr) { - return httpErr.Code == 401 || httpErr.Code == 403 - } - - var ippErr ipp.IPPError - if errors.As(err, &ippErr) { - return ippErr.Status == ipp.StatusErrorForbidden || - ippErr.Status == ipp.StatusErrorNotAuthenticated || - ippErr.Status == ipp.StatusErrorNotAuthorized - } - - return false -} - -func (m *Manager) GetPrinters() ([]Printer, error) { - attributes := []string{ - ipp.AttributePrinterName, - ipp.AttributePrinterUriSupported, - ipp.AttributePrinterState, - ipp.AttributePrinterStateReasons, - ipp.AttributePrinterLocation, - ipp.AttributePrinterInfo, - ipp.AttributePrinterMakeAndModel, - ipp.AttributePrinterIsAcceptingJobs, - } - - printerAttrs, err := m.client.GetPrinters(attributes) - if err != nil { - if isNoPrintersError(err) { - return []Printer{}, nil - } - return nil, err - } - - printers := make([]Printer, 0, len(printerAttrs)) - for _, attrs := range printerAttrs { - printer := Printer{ - Name: getStringAttr(attrs, ipp.AttributePrinterName), - URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported), - State: parsePrinterState(attrs), - StateReason: getStringAttr(attrs, ipp.AttributePrinterStateReasons), - Location: getStringAttr(attrs, ipp.AttributePrinterLocation), - Info: getStringAttr(attrs, ipp.AttributePrinterInfo), - MakeModel: getStringAttr(attrs, ipp.AttributePrinterMakeAndModel), - Accepting: getBoolAttr(attrs, ipp.AttributePrinterIsAcceptingJobs), - } - - if printer.Name != "" { - printers = append(printers, printer) - } - } - - return printers, nil -} - -func (m *Manager) GetJobs(printerName string, whichJobs string) ([]Job, error) { - attributes := []string{ - ipp.AttributeJobID, - ipp.AttributeJobName, - ipp.AttributeJobState, - ipp.AttributeJobPrinterURI, - ipp.AttributeJobOriginatingUserName, - ipp.AttributeJobKilobyteOctets, - "time-at-creation", - } - - jobAttrs, err := m.client.GetJobs(printerName, "", whichJobs, false, 0, 0, attributes) - if err != nil { - return nil, err - } - - jobs := make([]Job, 0, len(jobAttrs)) - for _, attrs := range jobAttrs { - job := Job{ - ID: getIntAttr(attrs, ipp.AttributeJobID), - Name: getStringAttr(attrs, ipp.AttributeJobName), - State: parseJobState(attrs), - User: getStringAttr(attrs, ipp.AttributeJobOriginatingUserName), - Size: getIntAttr(attrs, ipp.AttributeJobKilobyteOctets) * 1024, - } - - if uri := getStringAttr(attrs, ipp.AttributeJobPrinterURI); uri != "" { - parts := strings.Split(uri, "/") - if len(parts) > 0 { - job.Printer = parts[len(parts)-1] - } - } - - if ts := getIntAttr(attrs, "time-at-creation"); ts > 0 { - job.TimeCreated = time.Unix(int64(ts), 0) - } - - if job.ID != 0 { - jobs = append(jobs, job) - } - } - - return jobs, nil -} - -func (m *Manager) CancelJob(jobID int) error { - err := m.client.CancelJob(jobID, false) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.JobCancelPurge(jobID, false) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) PausePrinter(printerName string) error { - err := m.client.PausePrinter(printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetEnabled(printerName, false) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) ResumePrinter(printerName string) error { - err := m.client.ResumePrinter(printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetEnabled(printerName, true) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) PurgeJobs(printerName string) error { - err := m.client.CancelAllJob(printerName, true) - if err == nil { - m.RefreshState() - } - return err -} - -func resolveIPFromURI(uri string) string { - parsed, err := url.Parse(uri) - if err != nil { - return "" - } - host := parsed.Hostname() - if host == "" { - return "" - } - if ip := net.ParseIP(host); ip != nil { - return ip.String() - } - addrs, err := net.LookupIP(host) - if err != nil || len(addrs) == 0 { - return "" - } - for _, addr := range addrs { - if v4 := addr.To4(); v4 != nil { - return v4.String() - } - } - return addrs[0].String() -} - -func (m *Manager) GetDevices() ([]Device, error) { - if m.pkHelper != nil { - devices, err := m.pkHelper.DevicesGet(10, 0, nil, nil) - if err != nil { - return nil, err - } - for i := range devices { - if devices[i].Class == "network" { - devices[i].IP = resolveIPFromURI(devices[i].URI) - } - } - return devices, nil - } - - deviceAttrs, err := m.client.GetDevices() - if err != nil { - return nil, err - } - - devices := make([]Device, 0, len(deviceAttrs)) - for uri, attrs := range deviceAttrs { - device := Device{ - URI: uri, - Class: getStringAttr(attrs, "device-class"), - Info: getStringAttr(attrs, "device-info"), - MakeModel: getStringAttr(attrs, "device-make-and-model"), - ID: getStringAttr(attrs, "device-id"), - Location: getStringAttr(attrs, "device-location"), - } - if device.Class == "network" { - device.IP = resolveIPFromURI(uri) - } - devices = append(devices, device) - } - - return devices, nil -} - -func (m *Manager) GetPPDs() ([]PPD, error) { - ppdAttrs, err := m.client.GetPPDs() - if err != nil { - return nil, err - } - - ppds := make([]PPD, 0, len(ppdAttrs)) - for name, attrs := range ppdAttrs { - ppd := PPD{ - Name: name, - NaturalLanguage: getStringAttr(attrs, "ppd-natural-language"), - MakeModel: getStringAttr(attrs, ipp.AttributePPDMakeAndModel), - DeviceID: getStringAttr(attrs, "ppd-device-id"), - Product: getStringAttr(attrs, "ppd-product"), - PSVersion: getStringAttr(attrs, "ppd-psversion"), - Type: getStringAttr(attrs, "ppd-type"), - } - ppds = append(ppds, ppd) - } - - return ppds, nil -} - -func (m *Manager) GetClasses() ([]PrinterClass, error) { - attributes := []string{ - ipp.AttributePrinterName, - ipp.AttributePrinterUriSupported, - ipp.AttributePrinterState, - ipp.AttributeMemberURIs, - ipp.AttributeMemberNames, - ipp.AttributePrinterLocation, - ipp.AttributePrinterInfo, - } - - classAttrs, err := m.client.GetClasses(attributes) - if err != nil { - return nil, err - } - - classes := make([]PrinterClass, 0, len(classAttrs)) - for _, attrs := range classAttrs { - class := PrinterClass{ - Name: getStringAttr(attrs, ipp.AttributePrinterName), - URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported), - State: parsePrinterState(attrs), - Location: getStringAttr(attrs, ipp.AttributePrinterLocation), - Info: getStringAttr(attrs, ipp.AttributePrinterInfo), - Members: getStringSliceAttr(attrs, ipp.AttributeMemberNames), - } - classes = append(classes, class) - } - - return classes, nil -} - -func createPrinterViaLpadmin(name, deviceURI, ppd, information, location string) error { - args := []string{"-p", name, "-E", "-v", deviceURI, "-m", ppd} - if information != "" { - args = append(args, "-D", information) - } - if location != "" { - args = append(args, "-L", location) - } - out, err := exec.Command("lpadmin", args...).CombinedOutput() - if err != nil { - return fmt.Errorf("lpadmin failed: %s: %w", strings.TrimSpace(string(out)), err) - } - return nil -} - -func deletePrinterViaLpadmin(name string) error { - out, err := exec.Command("lpadmin", "-x", name).CombinedOutput() - if err != nil { - return fmt.Errorf("lpadmin failed: %s: %w", strings.TrimSpace(string(out)), err) - } - return nil -} - -func (m *Manager) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error { - usedPkHelper := false - - err := m.client.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location) - if isAuthError(err) && m.pkHelper != nil { - if err = m.pkHelper.PrinterAdd(name, deviceURI, ppd, information, location); err != nil { - // pkHelper failed (e.g., no polkit agent), try lpadmin as last resort. - // lpadmin -E enables the printer, so no further setup needed. - if lpadminErr := createPrinterViaLpadmin(name, deviceURI, ppd, information, location); lpadminErr != nil { - return err - } - m.RefreshState() - return nil - } - usedPkHelper = true - } else if err != nil { - return err - } - - if usedPkHelper { - m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck - m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck - } else { - if err := m.client.ResumePrinter(name); isAuthError(err) && m.pkHelper != nil { - m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck - } - if err := m.client.AcceptJobs(name); isAuthError(err) && m.pkHelper != nil { - m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck - } - } - - m.RefreshState() - return nil -} - -func (m *Manager) DeletePrinter(printerName string) error { - err := m.client.DeletePrinter(printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterDelete(printerName) - if err != nil { - // pkHelper failed, try lpadmin as last resort - if lpadminErr := deletePrinterViaLpadmin(printerName); lpadminErr == nil { - err = nil - } - } - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) AcceptJobs(printerName string) error { - err := m.client.AcceptJobs(printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetAcceptJobs(printerName, true, "") - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) RejectJobs(printerName string) error { - err := m.client.RejectJobs(printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetAcceptJobs(printerName, false, "") - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) SetPrinterShared(printerName string, shared bool) error { - err := m.client.SetPrinterIsShared(printerName, shared) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetShared(printerName, shared) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) SetPrinterLocation(printerName, location string) error { - err := m.client.SetPrinterLocation(printerName, location) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetLocation(printerName, location) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) SetPrinterInfo(printerName, info string) error { - err := m.client.SetPrinterInformation(printerName, info) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.PrinterSetInfo(printerName, info) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) MoveJob(jobID int, destPrinter string) error { - err := m.client.MoveJob(jobID, destPrinter) - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) PrintTestPage(printerName string) (int, error) { - jobID, err := m.client.PrintTestPage(printerName, strings.NewReader(config.TestPage), len(config.TestPage)) - if err == nil { - m.RefreshState() - } - return jobID, err -} - -func (m *Manager) AddPrinterToClass(className, printerName string) error { - err := m.client.AddPrinterToClass(className, printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.ClassAddPrinter(className, printerName) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) RemovePrinterFromClass(className, printerName string) error { - err := m.client.DeletePrinterFromClass(className, printerName) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.ClassDeletePrinter(className, printerName) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) DeleteClass(className string) error { - err := m.client.DeleteClass(className) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.ClassDelete(className) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) RestartJob(jobID int) error { - err := m.client.RestartJob(jobID) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.JobRestart(jobID) - } - if err == nil { - m.RefreshState() - } - return err -} - -func (m *Manager) HoldJob(jobID int, holdUntil string) error { - err := m.client.HoldJobUntil(jobID, holdUntil) - if isAuthError(err) && m.pkHelper != nil { - err = m.pkHelper.JobSetHoldUntil(jobID, holdUntil) - } - if err == nil { - m.RefreshState() - } - return err -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go deleted file mode 100644 index ad16a8a..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_pkhelper_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package cups_test - -import ( - "testing" - - mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups" - mocks_pkhelper "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups_pkhelper" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func authErr() error { - return ipp.IPPError{Status: ipp.StatusErrorForbidden} -} - -func TestManager_CancelJob_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelJob(1, false).Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().JobCancelPurge(1, false).Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.CancelJob(1)) -} - -func TestManager_CancelJob_PkHelperError(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelJob(1, false).Return(authErr()) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().JobCancelPurge(1, false).Return(assert.AnError) - - m := cups.NewTestManager(mockClient, mockPk) - assert.Error(t, m.CancelJob(1)) -} - -func TestManager_PausePrinter_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().PausePrinter("printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetEnabled("printer1", false).Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.PausePrinter("printer1")) -} - -func TestManager_ResumePrinter_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().ResumePrinter("printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetEnabled("printer1", true).Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.ResumePrinter("printer1")) -} - -func TestManager_GetDevices_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return([]cups.Device{ - {URI: "usb://HP/LaserJet", Class: "direct"}, - }, nil) - - m := cups.NewTestManager(mockClient, mockPk) - got, err := m.GetDevices() - assert.NoError(t, err) - assert.Len(t, got, 1) - assert.Equal(t, "usb://HP/LaserJet", got[0].URI) -} - -func TestManager_GetDevices_PkHelperError(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return(nil, assert.AnError) - - m := cups.NewTestManager(mockClient, mockPk) - _, err := m.GetDevices() - assert.Error(t, err) -} - -func TestManager_CreatePrinter_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterAdd("newprinter", "usb://HP", "generic.ppd", "info", "location").Return(nil) - mockPk.EXPECT().PrinterSetEnabled("newprinter", true).Return(nil) - mockPk.EXPECT().PrinterSetAcceptJobs("newprinter", true, "").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location")) -} - -func TestManager_DeletePrinter_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinter("printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterDelete("printer1").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.DeletePrinter("printer1")) -} - -func TestManager_AcceptJobs_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AcceptJobs("printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetAcceptJobs("printer1", true, "").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.AcceptJobs("printer1")) -} - -func TestManager_RejectJobs_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RejectJobs("printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetAcceptJobs("printer1", false, "").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.RejectJobs("printer1")) -} - -func TestManager_SetPrinterShared_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetShared("printer1", true).Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.SetPrinterShared("printer1", true)) -} - -func TestManager_SetPrinterLocation_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetLocation("printer1", "Office").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.SetPrinterLocation("printer1", "Office")) -} - -func TestManager_SetPrinterInfo_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().PrinterSetInfo("printer1", "Main Printer").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer")) -} - -func TestManager_AddPrinterToClass_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().ClassAddPrinter("office", "printer1").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.AddPrinterToClass("office", "printer1")) -} - -func TestManager_RemovePrinterFromClass_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().ClassDeletePrinter("office", "printer1").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.RemovePrinterFromClass("office", "printer1")) -} - -func TestManager_DeleteClass_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeleteClass("office").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().ClassDelete("office").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.DeleteClass("office")) -} - -func TestManager_RestartJob_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RestartJob(1).Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().JobRestart(1).Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.RestartJob(1)) -} - -func TestManager_HoldJob_WithPkHelper(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(authErr()) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - mockPk := mocks_pkhelper.NewMockPkHelper(t) - mockPk.EXPECT().JobSetHoldUntil(1, "indefinite").Return(nil) - - m := cups.NewTestManager(mockClient, mockPk) - assert.NoError(t, m.HoldJob(1, "indefinite")) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go deleted file mode 100644 index 70f4f21..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/actions_test.go +++ /dev/null @@ -1,450 +0,0 @@ -package cups - -import ( - "errors" - "testing" - "time" - - mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestManager_GetPrinters(t *testing.T) { - tests := []struct { - name string - mockRet map[string]ipp.Attributes - mockErr error - want int - wantErr bool - }{ - { - name: "success", - mockRet: map[string]ipp.Attributes{ - "printer1": { - ipp.AttributePrinterName: []ipp.Attribute{{Value: "printer1"}}, - ipp.AttributePrinterUriSupported: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}}, - ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}}, - ipp.AttributePrinterStateReasons: []ipp.Attribute{{Value: "none"}}, - ipp.AttributePrinterLocation: []ipp.Attribute{{Value: "Office"}}, - ipp.AttributePrinterInfo: []ipp.Attribute{{Value: "Test Printer"}}, - ipp.AttributePrinterMakeAndModel: []ipp.Attribute{{Value: "Generic"}}, - ipp.AttributePrinterIsAcceptingJobs: []ipp.Attribute{{Value: true}}, - }, - }, - mockErr: nil, - want: 1, - wantErr: false, - }, - { - name: "error", - mockRet: nil, - mockErr: errors.New("test error"), - want: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(tt.mockRet, tt.mockErr) - - m := &Manager{ - client: mockClient, - } - - got, err := m.GetPrinters() - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, len(got)) - if len(got) > 0 { - assert.Equal(t, "printer1", got[0].Name) - assert.Equal(t, "idle", got[0].State) - assert.Equal(t, "Office", got[0].Location) - assert.True(t, got[0].Accepting) - } - } - }) - } -} - -func TestManager_GetJobs(t *testing.T) { - tests := []struct { - name string - mockRet map[int]ipp.Attributes - mockErr error - want int - wantErr bool - }{ - { - name: "success", - mockRet: map[int]ipp.Attributes{ - 1: { - ipp.AttributeJobID: []ipp.Attribute{{Value: 1}}, - ipp.AttributeJobName: []ipp.Attribute{{Value: "test-job"}}, - ipp.AttributeJobState: []ipp.Attribute{{Value: 5}}, - ipp.AttributeJobPrinterURI: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}}, - ipp.AttributeJobOriginatingUserName: []ipp.Attribute{{Value: "testuser"}}, - ipp.AttributeJobKilobyteOctets: []ipp.Attribute{{Value: 10}}, - "time-at-creation": []ipp.Attribute{{Value: 1609459200}}, - }, - }, - mockErr: nil, - want: 1, - wantErr: false, - }, - { - name: "error", - mockRet: nil, - mockErr: errors.New("test error"), - want: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetJobs("printer1", "", "not-completed", false, 0, 0, mock.Anything). - Return(tt.mockRet, tt.mockErr) - - m := &Manager{ - client: mockClient, - } - - got, err := m.GetJobs("printer1", "not-completed") - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, len(got)) - if len(got) > 0 { - assert.Equal(t, 1, got[0].ID) - assert.Equal(t, "test-job", got[0].Name) - assert.Equal(t, "processing", got[0].State) - assert.Equal(t, "testuser", got[0].User) - assert.Equal(t, "printer1", got[0].Printer) - assert.Equal(t, 10240, got[0].Size) - assert.Equal(t, time.Unix(1609459200, 0), got[0].TimeCreated) - } - } - }) - } -} - -func TestManager_CancelJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelJob(1, false).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.CancelJob(1)) -} - -func TestManager_PausePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().PausePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.PausePrinter("printer1")) -} - -func TestManager_ResumePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().ResumePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.ResumePrinter("printer1")) -} - -func TestManager_PurgeJobs(t *testing.T) { - tests := []struct { - name string - mockErr error - wantErr bool - }{ - { - name: "success", - mockErr: nil, - wantErr: false, - }, - { - name: "error", - mockErr: errors.New("test error"), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr) - if !tt.wantErr { - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - } - - m := NewTestManager(mockClient, nil) - - err := m.PurgeJobs("printer1") - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestManager_GetDevices(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{ - "usb://HP/LaserJet": { - "device-class": []ipp.Attribute{{Value: "direct"}}, - "device-info": []ipp.Attribute{{Value: "HP LaserJet"}}, - "device-make-and-model": []ipp.Attribute{{Value: "HP LaserJet 1020"}}, - }, - }, nil) - - m := &Manager{client: mockClient} - got, err := m.GetDevices() - assert.NoError(t, err) - assert.Len(t, got, 1) - assert.Equal(t, "usb://HP/LaserJet", got[0].URI) - assert.Equal(t, "direct", got[0].Class) -} - -func TestManager_GetPPDs(t *testing.T) { - tests := []struct { - name string - mockRet map[string]ipp.Attributes - mockErr error - want int - wantErr bool - }{ - { - name: "success", - mockRet: map[string]ipp.Attributes{ - "drv:///sample.drv/generic.ppd": { - "ppd-make-and-model": []ipp.Attribute{{Value: "Generic PostScript"}}, - "ppd-type": []ipp.Attribute{{Value: "ppd"}}, - }, - }, - mockErr: nil, - want: 1, - wantErr: false, - }, - { - name: "error", - mockRet: nil, - mockErr: errors.New("test error"), - want: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetPPDs().Return(tt.mockRet, tt.mockErr) - - m := &Manager{client: mockClient} - - got, err := m.GetPPDs() - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Equal(t, tt.want, len(got)) - }) - } -} - -func TestManager_GetClasses(t *testing.T) { - tests := []struct { - name string - mockRet map[string]ipp.Attributes - mockErr error - want int - wantErr bool - }{ - { - name: "success", - mockRet: map[string]ipp.Attributes{ - "office": { - ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}}, - ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}}, - ipp.AttributeMemberNames: []ipp.Attribute{{Value: "printer1"}, {Value: "printer2"}}, - }, - }, - mockErr: nil, - want: 1, - wantErr: false, - }, - { - name: "error", - mockRet: nil, - mockErr: errors.New("test error"), - want: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetClasses(mock.Anything).Return(tt.mockRet, tt.mockErr) - - m := &Manager{client: mockClient} - - got, err := m.GetClasses() - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Equal(t, tt.want, len(got)) - if len(got) > 0 { - assert.Equal(t, "office", got[0].Name) - assert.Equal(t, 2, len(got[0].Members)) - } - }) - } -} - -func TestManager_CreatePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(nil) - mockClient.EXPECT().ResumePrinter("newprinter").Return(nil) - mockClient.EXPECT().AcceptJobs("newprinter").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location")) -} - -func TestManager_DeletePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.DeletePrinter("printer1")) -} - -func TestManager_AcceptJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AcceptJobs("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.AcceptJobs("printer1")) -} - -func TestManager_RejectJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RejectJobs("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.RejectJobs("printer1")) -} - -func TestManager_SetPrinterShared(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.SetPrinterShared("printer1", true)) -} - -func TestManager_SetPrinterLocation(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.SetPrinterLocation("printer1", "Office")) -} - -func TestManager_SetPrinterInfo(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer")) -} - -func TestManager_MoveJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().MoveJob(1, "printer2").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - err := m.MoveJob(1, "printer2") - assert.NoError(t, err) -} - -func TestManager_PrintTestPage(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - jobID, err := m.PrintTestPage("printer1") - assert.NoError(t, err) - assert.Equal(t, 42, jobID) -} - -func TestManager_AddPrinterToClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.AddPrinterToClass("office", "printer1")) -} - -func TestManager_RemovePrinterFromClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.RemovePrinterFromClass("office", "printer1")) -} - -func TestManager_DeleteClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeleteClass("office").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.DeleteClass("office")) -} - -func TestManager_RestartJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RestartJob(1).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.RestartJob(1)) -} - -func TestManager_HoldJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - assert.NoError(t, m.HoldJob(1, "indefinite")) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go deleted file mode 100644 index 57b45d5..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers.go +++ /dev/null @@ -1,487 +0,0 @@ -package cups - -import ( - "encoding/json" - "fmt" - "net" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" -) - -type CUPSEvent struct { - Type string `json:"type"` - Data CUPSState `json:"data"` -} - -type TestPageResult struct { - Success bool `json:"success"` - JobID int `json:"jobId"` - Message string `json:"message"` -} - -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { - switch req.Method { - case "cups.subscribe": - handleSubscribe(conn, req, manager) - case "cups.getPrinters": - handleGetPrinters(conn, req, manager) - case "cups.getJobs": - handleGetJobs(conn, req, manager) - case "cups.pausePrinter": - handlePausePrinter(conn, req, manager) - case "cups.resumePrinter": - handleResumePrinter(conn, req, manager) - case "cups.cancelJob": - handleCancelJob(conn, req, manager) - case "cups.purgeJobs": - handlePurgeJobs(conn, req, manager) - case "cups.getDevices": - handleGetDevices(conn, req, manager) - case "cups.getPPDs": - handleGetPPDs(conn, req, manager) - case "cups.getClasses": - handleGetClasses(conn, req, manager) - case "cups.createPrinter": - handleCreatePrinter(conn, req, manager) - case "cups.deletePrinter": - handleDeletePrinter(conn, req, manager) - case "cups.acceptJobs": - handleAcceptJobs(conn, req, manager) - case "cups.rejectJobs": - handleRejectJobs(conn, req, manager) - case "cups.setPrinterShared": - handleSetPrinterShared(conn, req, manager) - case "cups.setPrinterLocation": - handleSetPrinterLocation(conn, req, manager) - case "cups.setPrinterInfo": - handleSetPrinterInfo(conn, req, manager) - case "cups.moveJob": - handleMoveJob(conn, req, manager) - case "cups.printTestPage": - handlePrintTestPage(conn, req, manager) - case "cups.addPrinterToClass": - handleAddPrinterToClass(conn, req, manager) - case "cups.removePrinterFromClass": - handleRemovePrinterFromClass(conn, req, manager) - case "cups.deleteClass": - handleDeleteClass(conn, req, manager) - case "cups.restartJob": - handleRestartJob(conn, req, manager) - case "cups.holdJob": - handleHoldJob(conn, req, manager) - case "cups.testConnection": - handleTestConnection(conn, req, manager) - default: - models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method)) - } -} - -func handleGetPrinters(conn net.Conn, req models.Request, manager *Manager) { - printers, err := manager.GetPrinters() - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, printers) -} - -func handleGetJobs(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.String(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - jobs, err := manager.GetJobs(printerName, "not-completed") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, jobs) -} - -func handlePausePrinter(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.String(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.PausePrinter(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paused"}) -} - -func handleResumePrinter(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.String(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.ResumePrinter(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "resumed"}) -} - -func handleCancelJob(conn net.Conn, req models.Request, manager *Manager) { - jobID, err := params.Int(req.Params, "jobID") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.CancelJob(jobID); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job canceled"}) -} - -func handlePurgeJobs(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.String(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.PurgeJobs(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "jobs canceled"}) -} - -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { - clientID := fmt.Sprintf("client-%p", conn) - stateChan := manager.Subscribe(clientID) - defer manager.Unsubscribe(clientID) - - initialState := manager.GetState() - event := CUPSEvent{ - Type: "state_changed", - Data: initialState, - } - - if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{ - ID: req.ID, - Result: &event, - }); err != nil { - return - } - - for state := range stateChan { - event := CUPSEvent{ - Type: "state_changed", - Data: state, - } - if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{ - Result: &event, - }); err != nil { - return - } - } -} - -func handleGetDevices(conn net.Conn, req models.Request, manager *Manager) { - devices, err := manager.GetDevices() - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, devices) -} - -func handleGetPPDs(conn net.Conn, req models.Request, manager *Manager) { - ppds, err := manager.GetPPDs() - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, ppds) -} - -func handleGetClasses(conn net.Conn, req models.Request, manager *Manager) { - classes, err := manager.GetClasses() - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, classes) -} - -func handleCreatePrinter(conn net.Conn, req models.Request, manager *Manager) { - name, err := params.StringNonEmpty(req.Params, "name") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - deviceURI, err := params.StringNonEmpty(req.Params, "deviceURI") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - ppd, err := params.StringNonEmpty(req.Params, "ppd") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - shared := params.BoolOpt(req.Params, "shared", false) - errorPolicy := params.StringOpt(req.Params, "errorPolicy", "") - information := params.StringOpt(req.Params, "information", "") - location := params.StringOpt(req.Params, "location", "") - - if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer created"}) -} - -func handleDeletePrinter(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.DeletePrinter(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer deleted"}) -} - -func handleAcceptJobs(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.AcceptJobs(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "accepting jobs"}) -} - -func handleRejectJobs(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.RejectJobs(printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "rejecting jobs"}) -} - -func handleSetPrinterShared(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - shared, err := params.Bool(req.Params, "shared") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.SetPrinterShared(printerName, shared); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sharing updated"}) -} - -func handleSetPrinterLocation(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - location, err := params.String(req.Params, "location") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.SetPrinterLocation(printerName, location); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location updated"}) -} - -func handleSetPrinterInfo(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - info, err := params.String(req.Params, "info") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.SetPrinterInfo(printerName, info); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "info updated"}) -} - -func handleMoveJob(conn net.Conn, req models.Request, manager *Manager) { - jobID, err := params.Int(req.Params, "jobID") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - destPrinter, err := params.StringNonEmpty(req.Params, "destPrinter") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.MoveJob(jobID, destPrinter); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job moved"}) -} - -func handlePrintTestPage(conn net.Conn, req models.Request, manager *Manager) { - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - jobID, err := manager.PrintTestPage(printerName) - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"}) -} - -func handleAddPrinterToClass(conn net.Conn, req models.Request, manager *Manager) { - className, err := params.StringNonEmpty(req.Params, "className") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.AddPrinterToClass(className, printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer added to class"}) -} - -func handleRemovePrinterFromClass(conn net.Conn, req models.Request, manager *Manager) { - className, err := params.StringNonEmpty(req.Params, "className") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - printerName, err := params.StringNonEmpty(req.Params, "printerName") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.RemovePrinterFromClass(className, printerName); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer removed from class"}) -} - -func handleDeleteClass(conn net.Conn, req models.Request, manager *Manager) { - className, err := params.StringNonEmpty(req.Params, "className") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.DeleteClass(className); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "class deleted"}) -} - -func handleRestartJob(conn net.Conn, req models.Request, manager *Manager) { - jobID, err := params.Int(req.Params, "jobID") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - if err := manager.RestartJob(jobID); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job restarted"}) -} - -func handleHoldJob(conn net.Conn, req models.Request, manager *Manager) { - jobID, err := params.Int(req.Params, "jobID") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - holdUntil := params.StringOpt(req.Params, "holdUntil", "indefinite") - - if err := manager.HoldJob(jobID, holdUntil); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job held"}) -} - -func handleTestConnection(conn net.Conn, req models.Request, manager *Manager) { - host, err := params.StringNonEmpty(req.Params, "host") - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - port := params.IntOpt(req.Params, "port", 631) - protocol := params.StringOpt(req.Params, "protocol", "ipp") - - result, err := manager.TestRemotePrinter(host, port, protocol) - if err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - models.Respond(conn, req.ID, result) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go deleted file mode 100644 index b73931e..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/handlers_test.go +++ /dev/null @@ -1,711 +0,0 @@ -package cups - -import ( - "bytes" - "encoding/json" - "errors" - "net" - "testing" - "time" - - mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -type mockConn struct { - *bytes.Buffer -} - -func (m *mockConn) Close() error { return nil } -func (m *mockConn) LocalAddr() net.Addr { return nil } -func (m *mockConn) RemoteAddr() net.Addr { return nil } -func (m *mockConn) SetDeadline(t time.Time) error { return nil } -func (m *mockConn) SetReadDeadline(t time.Time) error { return nil } -func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil } - -func TestHandleGetPrinters(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{ - "printer1": { - ipp.AttributePrinterName: []ipp.Attribute{{Value: "printer1"}}, - ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}}, - ipp.AttributePrinterUriSupported: []ipp.Attribute{{Value: "ipp://localhost/printers/printer1"}}, - }, - }, nil) - - m := &Manager{ - client: mockClient, - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.getPrinters", - } - - handleGetPrinters(conn, req, m) - - var resp models.Response[[]Printer] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.Equal(t, 1, len(*resp.Result)) -} - -func TestHandleGetPrinters_Error(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(nil, errors.New("test error")) - - m := &Manager{ - client: mockClient, - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.getPrinters", - } - - handleGetPrinters(conn, req, m) - - var resp models.Response[any] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Error) -} - -func TestHandleGetJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetJobs("printer1", "", "not-completed", false, 0, 0, mock.Anything). - Return(map[int]ipp.Attributes{ - 1: { - ipp.AttributeJobID: []ipp.Attribute{{Value: 1}}, - ipp.AttributeJobName: []ipp.Attribute{{Value: "job1"}}, - ipp.AttributeJobState: []ipp.Attribute{{Value: 5}}, - }, - }, nil) - - m := &Manager{ - client: mockClient, - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.getJobs", - Params: map[string]any{ - "printerName": "printer1", - }, - } - - handleGetJobs(conn, req, m) - - var resp models.Response[[]Job] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.Equal(t, 1, len(*resp.Result)) -} - -func TestHandleGetJobs_MissingParam(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - m := &Manager{ - client: mockClient, - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.getJobs", - Params: map[string]any{}, - } - - handleGetJobs(conn, req, m) - - var resp models.Response[any] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Error) -} - -func TestHandlePausePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().PausePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.pausePrinter", - Params: map[string]any{ - "printerName": "printer1", - }, - } - - handlePausePrinter(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleResumePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().ResumePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.resumePrinter", - Params: map[string]any{ - "printerName": "printer1", - }, - } - - handleResumePrinter(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleCancelJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelJob(1, false).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.cancelJob", - Params: map[string]any{ - "jobID": float64(1), - }, - } - - handleCancelJob(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandlePurgeJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.purgeJobs", - Params: map[string]any{ - "printerName": "printer1", - }, - } - - handlePurgeJobs(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleRequest_UnknownMethod(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - m := &Manager{ - client: mockClient, - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.unknownMethod", - } - - HandleRequest(conn, req, m) - - var resp models.Response[any] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Error) -} - -func TestHandleGetDevices(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{ - "usb://HP/LaserJet": { - "device-class": []ipp.Attribute{{Value: "direct"}}, - "device-info": []ipp.Attribute{{Value: "HP LaserJet"}}, - }, - }, nil) - - m := &Manager{client: mockClient} - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ID: 1, Method: "cups.getDevices"} - handleGetDevices(conn, req, m) - - var resp models.Response[[]Device] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.Equal(t, 1, len(*resp.Result)) -} - -func TestHandleGetPPDs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetPPDs().Return(map[string]ipp.Attributes{ - "generic.ppd": { - "ppd-make-and-model": []ipp.Attribute{{Value: "Generic"}}, - }, - }, nil) - - m := &Manager{client: mockClient} - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ID: 1, Method: "cups.getPPDs"} - handleGetPPDs(conn, req, m) - - var resp models.Response[[]PPD] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.Equal(t, 1, len(*resp.Result)) -} - -func TestHandleGetClasses(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().GetClasses(mock.Anything).Return(map[string]ipp.Attributes{ - "office": { - ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}}, - ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}}, - }, - }, nil) - - m := &Manager{client: mockClient} - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ID: 1, Method: "cups.getClasses"} - handleGetClasses(conn, req, m) - - var resp models.Response[[]PrinterClass] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.Equal(t, 1, len(*resp.Result)) -} - -func TestHandleCreatePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", false, "", "", "").Return(nil) - mockClient.EXPECT().ResumePrinter("newprinter").Return(nil) - mockClient.EXPECT().AcceptJobs("newprinter").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.createPrinter", - Params: map[string]any{ - "name": "newprinter", - "deviceURI": "usb://HP", - "ppd": "generic.ppd", - }, - } - handleCreatePrinter(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleCreatePrinter_MissingParams(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - m := &Manager{client: mockClient} - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ID: 1, Method: "cups.createPrinter", Params: map[string]any{}} - handleCreatePrinter(conn, req, m) - - var resp models.Response[any] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Error) -} - -func TestHandleDeletePrinter(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinter("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.deletePrinter", - Params: map[string]any{"printerName": "printer1"}, - } - handleDeletePrinter(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleAcceptJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AcceptJobs("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.acceptJobs", - Params: map[string]any{"printerName": "printer1"}, - } - handleAcceptJobs(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleRejectJobs(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RejectJobs("printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.rejectJobs", - Params: map[string]any{"printerName": "printer1"}, - } - handleRejectJobs(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleSetPrinterShared(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.setPrinterShared", - Params: map[string]any{"printerName": "printer1", "shared": true}, - } - handleSetPrinterShared(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleSetPrinterLocation(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.setPrinterLocation", - Params: map[string]any{"printerName": "printer1", "location": "Office"}, - } - handleSetPrinterLocation(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleSetPrinterInfo(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.setPrinterInfo", - Params: map[string]any{"printerName": "printer1", "info": "Main Printer"}, - } - handleSetPrinterInfo(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleMoveJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().MoveJob(1, "printer2").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.moveJob", - Params: map[string]any{"jobID": float64(1), "destPrinter": "printer2"}, - } - handleMoveJob(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandlePrintTestPage(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.printTestPage", - Params: map[string]any{"printerName": "printer1"}, - } - handlePrintTestPage(conn, req, m) - - var resp models.Response[TestPageResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) - assert.Equal(t, 42, resp.Result.JobID) -} - -func TestHandleAddPrinterToClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.addPrinterToClass", - Params: map[string]any{"className": "office", "printerName": "printer1"}, - } - handleAddPrinterToClass(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleRemovePrinterFromClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.removePrinterFromClass", - Params: map[string]any{"className": "office", "printerName": "printer1"}, - } - handleRemovePrinterFromClass(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleDeleteClass(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().DeleteClass("office").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.deleteClass", - Params: map[string]any{"className": "office"}, - } - handleDeleteClass(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleRestartJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().RestartJob(1).Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.restartJob", - Params: map[string]any{"jobID": float64(1)}, - } - handleRestartJob(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleHoldJob(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.holdJob", - Params: map[string]any{"jobID": float64(1)}, - } - handleHoldJob(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} - -func TestHandleHoldJob_WithHoldUntil(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - mockClient.EXPECT().HoldJobUntil(1, "no-hold").Return(nil) - mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil) - - m := NewTestManager(mockClient, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.holdJob", - Params: map[string]any{"jobID": float64(1), "holdUntil": "no-hold"}, - } - handleHoldJob(conn, req, m) - - var resp models.Response[models.SuccessResult] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Success) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go deleted file mode 100644 index e5b7e8c..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager.go +++ /dev/null @@ -1,390 +0,0 @@ -package cups - -import ( - "errors" - "fmt" - "os" - "strconv" - "sync" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/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) - - var pkHelper PkHelper - if isLocalCUPS(host) { - var err error - pkHelper, err = NewPkHelper() - if err != nil { - log.Warnf("[CUPS] Failed to initialize pkhelper: %v", err) - } - } - - m := &Manager{ - state: &CUPSState{ - Printers: make(map[string]*Printer), - }, - client: client, - pkHelper: pkHelper, - baseURL: baseURL, - stateMutex: sync.RWMutex{}, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - 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 { - if isNoPrintersError(err) { - m.stateMutex.Lock() - m.state.Printers = make(map[string]*Printer) - m.stateMutex.Unlock() - return 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 isNoPrintersError(err error) bool { - if err == nil { - return false - } - - var ippErr ipp.IPPError - if errors.As(err, &ippErr) { - return ippErr.Status == 1030 - } - - return false -} - -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 - } - - currentState := m.snapshotState() - - if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) { - pending = false - continue - } - - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - select { - case ch <- currentState: - default: - } - return true - }) - - stateCopy := currentState - m.lastNotifiedState = &stateCopy - pending = false - } - } -} - -func (m *Manager) notifySubscribers() { - select { - case m.dirty <- struct{}{}: - default: - } -} - -func (m *Manager) RefreshState() { - if err := m.updateState(); err != nil { - log.Warnf("[CUPS] Failed to refresh state: %v", err) - return - } - m.notifySubscribers() -} - -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) - - wasEmpty := true - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - wasEmpty = false - return false - }) - - m.subscribers.Store(id, ch) - - 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) { - if val, ok := m.subscribers.LoadAndDelete(id); ok { - close(val) - } - - isEmpty := true - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - isEmpty = false - return false - }) - - 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.subscribers.Range(func(key string, ch chan CUPSState) bool { - close(ch) - m.subscribers.Delete(key) - return true - }) -} - -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 || - oldPrinter.Accepting != newPrinter.Accepting || - 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 -} - -func getStringSliceAttr(attrs ipp.Attributes, key string) []string { - attr, ok := attrs[key] - if !ok { - return nil - } - - result := make([]string, 0, len(attr)) - for _, a := range attr { - if val, ok := a.Value.(string); ok { - result = append(result, val) - } - } - return result -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go deleted file mode 100644 index 7f09e67..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/manager_test.go +++ /dev/null @@ -1,363 +0,0 @@ -package cups - -import ( - "testing" - - mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/stretchr/testify/assert" -) - -func TestNewManager(t *testing.T) { - m := &Manager{ - state: &CUPSState{ - Printers: make(map[string]*Printer), - }, - client: nil, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - assert.NotNil(t, m) - assert.NotNil(t, m.state) -} - -func TestManager_GetState(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - m := &Manager{ - state: &CUPSState{ - Printers: map[string]*Printer{ - "test-printer": { - Name: "test-printer", - State: "idle", - }, - }, - }, - client: mockClient, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - state := m.GetState() - assert.Equal(t, 1, len(state.Printers)) - assert.Equal(t, "test-printer", state.Printers["test-printer"].Name) -} - -func TestManager_Subscribe(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - m := &Manager{ - state: &CUPSState{ - Printers: make(map[string]*Printer), - }, - client: mockClient, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - ch := m.Subscribe("test-client") - assert.NotNil(t, ch) - - count := 0 - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - count++ - return true - }) - assert.Equal(t, 1, count) - - m.Unsubscribe("test-client") - count = 0 - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - count++ - return true - }) - assert.Equal(t, 0, count) -} - -func TestManager_Close(t *testing.T) { - mockClient := mocks_cups.NewMockCUPSClientInterface(t) - - m := &Manager{ - state: &CUPSState{ - Printers: make(map[string]*Printer), - }, - client: mockClient, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - m.eventWG.Add(1) - go func() { - defer m.eventWG.Done() - <-m.stopChan - }() - - m.notifierWg.Add(1) - go func() { - defer m.notifierWg.Done() - <-m.stopChan - }() - - m.Close() - count := 0 - m.subscribers.Range(func(key string, ch chan CUPSState) bool { - count++ - return true - }) - assert.Equal(t, 0, count) -} - -func TestStateChanged(t *testing.T) { - tests := []struct { - name string - oldState *CUPSState - newState *CUPSState - want bool - }{ - { - name: "no change", - oldState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "idle"}, - }, - }, - newState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "idle"}, - }, - }, - want: false, - }, - { - name: "state changed", - oldState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "idle"}, - }, - }, - newState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "processing"}, - }, - }, - want: true, - }, - { - name: "printer added", - oldState: &CUPSState{ - Printers: map[string]*Printer{}, - }, - newState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "idle"}, - }, - }, - want: true, - }, - { - name: "printer removed", - oldState: &CUPSState{ - Printers: map[string]*Printer{ - "p1": {Name: "p1", State: "idle"}, - }, - }, - newState: &CUPSState{ - Printers: map[string]*Printer{}, - }, - want: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := stateChanged(tt.oldState, tt.newState) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestParsePrinterState(t *testing.T) { - tests := []struct { - name string - attrs ipp.Attributes - want string - }{ - { - name: "idle", - attrs: ipp.Attributes{ - ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}}, - }, - want: "idle", - }, - { - name: "processing", - attrs: ipp.Attributes{ - ipp.AttributePrinterState: []ipp.Attribute{{Value: 4}}, - }, - want: "processing", - }, - { - name: "stopped", - attrs: ipp.Attributes{ - ipp.AttributePrinterState: []ipp.Attribute{{Value: 5}}, - }, - want: "stopped", - }, - { - name: "unknown", - attrs: ipp.Attributes{}, - want: "unknown", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := parsePrinterState(tt.attrs) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestParseJobState(t *testing.T) { - tests := []struct { - name string - attrs ipp.Attributes - want string - }{ - { - name: "pending", - attrs: ipp.Attributes{ - ipp.AttributeJobState: []ipp.Attribute{{Value: 3}}, - }, - want: "pending", - }, - { - name: "processing", - attrs: ipp.Attributes{ - ipp.AttributeJobState: []ipp.Attribute{{Value: 5}}, - }, - want: "processing", - }, - { - name: "completed", - attrs: ipp.Attributes{ - ipp.AttributeJobState: []ipp.Attribute{{Value: 9}}, - }, - want: "completed", - }, - { - name: "unknown", - attrs: ipp.Attributes{}, - want: "unknown", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := parseJobState(tt.attrs) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestGetStringAttr(t *testing.T) { - tests := []struct { - name string - attrs ipp.Attributes - key string - want string - }{ - { - name: "string value", - attrs: ipp.Attributes{ - "test-key": []ipp.Attribute{{Value: "test-value"}}, - }, - key: "test-key", - want: "test-value", - }, - { - name: "missing key", - attrs: ipp.Attributes{}, - key: "missing", - want: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := getStringAttr(tt.attrs, tt.key) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestGetIntAttr(t *testing.T) { - tests := []struct { - name string - attrs ipp.Attributes - key string - want int - }{ - { - name: "int value", - attrs: ipp.Attributes{ - "test-key": []ipp.Attribute{{Value: 42}}, - }, - key: "test-key", - want: 42, - }, - { - name: "missing key", - attrs: ipp.Attributes{}, - key: "missing", - want: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := getIntAttr(tt.attrs, tt.key) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestGetBoolAttr(t *testing.T) { - tests := []struct { - name string - attrs ipp.Attributes - key string - want bool - }{ - { - name: "true value", - attrs: ipp.Attributes{ - "test-key": []ipp.Attribute{{Value: true}}, - }, - key: "test-key", - want: true, - }, - { - name: "false value", - attrs: ipp.Attributes{ - "test-key": []ipp.Attribute{{Value: false}}, - }, - key: "test-key", - want: false, - }, - { - name: "missing key", - attrs: ipp.Attributes{}, - key: "missing", - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := getBoolAttr(tt.attrs, tt.key) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go deleted file mode 100644 index 81dd458..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper.go +++ /dev/null @@ -1,184 +0,0 @@ -package cups - -import ( - "fmt" - "strings" - - "github.com/godbus/dbus/v5" -) - -const ( - pkHelperDest = "org.opensuse.CupsPkHelper.Mechanism" - pkHelperPath = "/" - pkHelperInterface = "org.opensuse.CupsPkHelper.Mechanism" -) - -type PkHelper interface { - DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error) - PrinterAdd(name, uri, ppd, info, location string) error - PrinterDelete(name string) error - PrinterSetEnabled(name string, enabled bool) error - PrinterSetAcceptJobs(name string, enabled bool, reason string) error - PrinterSetInfo(name, info string) error - PrinterSetLocation(name, location string) error - PrinterSetShared(name string, shared bool) error - ClassAddPrinter(className, printerName string) error - ClassDeletePrinter(className, printerName string) error - ClassDelete(className string) error - JobCancelPurge(jobID int, purge bool) error - JobRestart(jobID int) error - JobSetHoldUntil(jobID int, holdUntil string) error -} - -type DBusPkHelper struct { - conn *dbus.Conn - obj dbus.BusObject -} - -func NewPkHelper() (*DBusPkHelper, error) { - conn, err := dbus.SystemBus() - if err != nil { - return nil, fmt.Errorf("failed to connect to system bus: %w", err) - } - - return &DBusPkHelper{ - conn: conn, - obj: conn.Object(pkHelperDest, pkHelperPath), - }, nil -} - -func (p *DBusPkHelper) DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error) { - if includeSchemes == nil { - includeSchemes = []string{} - } - if excludeSchemes == nil { - excludeSchemes = []string{} - } - - var errStr string - var devicesMap map[string]string - - call := p.obj.Call(pkHelperInterface+".DevicesGet", 0, int32(timeout), int32(limit), includeSchemes, excludeSchemes) - if call.Err != nil { - return nil, call.Err - } - if err := call.Store(&errStr, &devicesMap); err != nil { - return nil, err - } - if errStr != "" { - return nil, fmt.Errorf("%s", errStr) - } - - return parseDevicesMap(devicesMap), nil -} - -func parseDevicesMap(devicesMap map[string]string) []Device { - devicesByIndex := make(map[string]*Device) - - for key, value := range devicesMap { - idx := strings.LastIndex(key, ":") - if idx == -1 { - continue - } - - attr := key[:idx] - index := key[idx+1:] - - dev, ok := devicesByIndex[index] - if !ok { - dev = &Device{} - devicesByIndex[index] = dev - } - - switch attr { - case "device-uri": - dev.URI = value - case "device-class": - dev.Class = value - case "device-info": - dev.Info = value - case "device-make-and-model": - dev.MakeModel = value - case "device-id": - dev.ID = value - case "device-location": - dev.Location = value - } - } - - devices := make([]Device, 0, len(devicesByIndex)) - for _, dev := range devicesByIndex { - if dev.URI != "" { - devices = append(devices, *dev) - } - } - return devices -} - -func (p *DBusPkHelper) PrinterAdd(name, uri, ppd, info, location string) error { - return p.callSimple("PrinterAdd", name, uri, ppd, info, location) -} - -func (p *DBusPkHelper) PrinterDelete(name string) error { - return p.callSimple("PrinterDelete", name) -} - -func (p *DBusPkHelper) PrinterSetEnabled(name string, enabled bool) error { - return p.callSimple("PrinterSetEnabled", name, enabled) -} - -func (p *DBusPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error { - return p.callSimple("PrinterSetAcceptJobs", name, enabled, reason) -} - -func (p *DBusPkHelper) PrinterSetInfo(name, info string) error { - return p.callSimple("PrinterSetInfo", name, info) -} - -func (p *DBusPkHelper) PrinterSetLocation(name, location string) error { - return p.callSimple("PrinterSetLocation", name, location) -} - -func (p *DBusPkHelper) PrinterSetShared(name string, shared bool) error { - return p.callSimple("PrinterSetShared", name, shared) -} - -func (p *DBusPkHelper) ClassAddPrinter(className, printerName string) error { - return p.callSimple("ClassAddPrinter", className, printerName) -} - -func (p *DBusPkHelper) ClassDeletePrinter(className, printerName string) error { - return p.callSimple("ClassDeletePrinter", className, printerName) -} - -func (p *DBusPkHelper) ClassDelete(className string) error { - return p.callSimple("ClassDelete", className) -} - -func (p *DBusPkHelper) JobCancelPurge(jobID int, purge bool) error { - return p.callSimple("JobCancelPurge", int32(jobID), purge) -} - -func (p *DBusPkHelper) JobRestart(jobID int) error { - return p.callSimple("JobRestart", int32(jobID)) -} - -func (p *DBusPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error { - return p.callSimple("JobSetHoldUntil", int32(jobID), holdUntil) -} - -func (p *DBusPkHelper) callSimple(method string, args ...any) error { - var errStr string - - call := p.obj.Call(pkHelperInterface+"."+method, 0, args...) - if call.Err != nil { - return call.Err - } - if err := call.Store(&errStr); err != nil { - return err - } - if errStr != "" { - return fmt.Errorf("%s", errStr) - } - return nil -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go deleted file mode 100644 index 596f1d0..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/pkhelper_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package cups - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseDevicesMap(t *testing.T) { - tests := []struct { - name string - input map[string]string - wantLen int - wantURIs []string - }{ - { - name: "empty", - input: map[string]string{}, - wantLen: 0, - wantURIs: nil, - }, - { - name: "single_device", - input: map[string]string{ - "device-uri:0": "usb://HP/LaserJet", - "device-class:0": "direct", - "device-info:0": "HP LaserJet", - "device-make-and-model:0": "HP LaserJet 1020", - "device-id:0": "MFG:HP;MDL:LaserJet", - }, - wantLen: 1, - wantURIs: []string{"usb://HP/LaserJet"}, - }, - { - name: "multiple_devices", - input: map[string]string{ - "device-uri:0": "usb://HP/LaserJet", - "device-class:0": "direct", - "device-info:0": "HP LaserJet", - "device-uri:1": "socket://192.168.1.100", - "device-class:1": "network", - "device-info:1": "Network Printer", - }, - wantLen: 2, - wantURIs: []string{"usb://HP/LaserJet", "socket://192.168.1.100"}, - }, - { - name: "malformed_key", - input: map[string]string{ - "no-colon-here": "value", - }, - wantLen: 0, - wantURIs: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := parseDevicesMap(tt.input) - assert.Len(t, got, tt.wantLen) - - if tt.wantURIs != nil { - gotURIs := make(map[string]bool) - for _, d := range got { - gotURIs[d.URI] = true - } - for _, uri := range tt.wantURIs { - assert.True(t, gotURIs[uri], "expected URI %s not found", uri) - } - } - }) - } -} - -func TestParseDevicesMap_Attributes(t *testing.T) { - input := map[string]string{ - "device-uri:0": "usb://HP/LaserJet", - "device-class:0": "direct", - "device-info:0": "HP LaserJet", - "device-make-and-model:0": "HP LaserJet 1020", - "device-id:0": "MFG:HP;MDL:LaserJet", - "device-location:0": "USB Port", - } - - got := parseDevicesMap(input) - assert.Len(t, got, 1) - - dev := got[0] - assert.Equal(t, "usb://HP/LaserJet", dev.URI) - assert.Equal(t, "direct", dev.Class) - assert.Equal(t, "HP LaserJet", dev.Info) - assert.Equal(t, "HP LaserJet 1020", dev.MakeModel) - assert.Equal(t, "MFG:HP;MDL:LaserJet", dev.ID) - assert.Equal(t, "USB Port", dev.Location) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go deleted file mode 100644 index 6b369b4..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription.go +++ /dev/null @@ -1,245 +0,0 @@ -package cups - -import ( - "fmt" - "sync" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" -) - -type SubscriptionManager struct { - client CUPSClientInterface - subscriptionID int - sequenceNumber int - eventChan chan SubscriptionEvent - stopChan chan struct{} - wg sync.WaitGroup - baseURL string - running bool - mu sync.Mutex -} - -func NewSubscriptionManager(client CUPSClientInterface, baseURL string) *SubscriptionManager { - return &SubscriptionManager{ - client: client, - eventChan: make(chan SubscriptionEvent, 100), - stopChan: make(chan struct{}), - baseURL: baseURL, - } -} - -func (sm *SubscriptionManager) Start() error { - sm.mu.Lock() - if sm.running { - sm.mu.Unlock() - return fmt.Errorf("subscription manager already running") - } - sm.running = true - sm.mu.Unlock() - - subID, err := sm.createSubscription() - if err != nil { - sm.mu.Lock() - sm.running = false - sm.mu.Unlock() - return fmt.Errorf("failed to create subscription: %w", err) - } - - sm.subscriptionID = subID - log.Infof("[CUPS] Created IPP subscription with ID %d", subID) - - sm.wg.Add(1) - go sm.notificationLoop() - - return nil -} - -func (sm *SubscriptionManager) createSubscription() (int, error) { - req := ipp.NewRequest(ipp.OperationCreatePrinterSubscriptions, 1) - req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL) - req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms" - - // Subscription attributes go in SubscriptionAttributes (subscription-attributes-tag in IPP) - req.SubscriptionAttributes = map[string]any{ - "notify-events": []string{ - "printer-state-changed", - "printer-added", - "printer-deleted", - "job-created", - "job-completed", - "job-state-changed", - }, - "notify-pull-method": "ippget", - "notify-lease-duration": 0, - } - - // Send to root IPP endpoint - resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil) - if err != nil { - return 0, fmt.Errorf("SendRequest failed: %w", err) - } - - // Check for IPP errors - if err := resp.CheckForErrors(); err != nil { - return 0, fmt.Errorf("IPP error: %w", err) - } - - // Subscription ID comes back in SubscriptionAttributes - 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 *SubscriptionManager) notificationLoop() { - defer sm.wg.Done() - - backoff := 1 * time.Second - - for { - select { - case <-sm.stopChan: - return - default: - } - - gotAny, err := sm.fetchNotificationsWithWait() - if err != nil { - log.Warnf("[CUPS] Error fetching notifications: %v", err) - jitter := time.Duration(50+(time.Now().UnixNano()%200)) * time.Millisecond - sleepTime := backoff + jitter - if sleepTime > 30*time.Second { - sleepTime = 30 * time.Second - } - select { - case <-sm.stopChan: - return - case <-time.After(sleepTime): - } - if backoff < 30*time.Second { - backoff *= 2 - } - continue - } - - backoff = 1 * time.Second - - if gotAny { - continue - } - - select { - case <-sm.stopChan: - return - case <-time.After(2 * time.Second): - } - } -} - -func (sm *SubscriptionManager) fetchNotificationsWithWait() (bool, error) { - req := ipp.NewRequest(ipp.OperationGetNotifications, 1) - req.OperationAttributes[ipp.AttributePrinterURI] = fmt.Sprintf("%s/", sm.baseURL) - req.OperationAttributes[ipp.AttributeRequestingUserName] = "dms" - req.OperationAttributes["notify-subscription-ids"] = sm.subscriptionID - if sm.sequenceNumber > 0 { - req.OperationAttributes["notify-sequence-numbers"] = sm.sequenceNumber - } - - resp, err := sm.client.SendRequest(fmt.Sprintf("%s/", sm.baseURL), req, nil) - if err != nil { - return false, err - } - - gotAny := false - for _, eventGroup := range resp.SubscriptionAttributes { - if seqAttr, ok := eventGroup["notify-sequence-number"]; ok && len(seqAttr) > 0 { - if seqNum, ok := seqAttr[0].Value.(int); ok { - sm.sequenceNumber = seqNum + 1 - } - } - - event := sm.parseEvent(eventGroup) - gotAny = true - select { - case sm.eventChan <- event: - case <-sm.stopChan: - return gotAny, nil - default: - log.Warn("[CUPS] Event channel full, dropping event") - } - } - - return gotAny, nil -} - -func (sm *SubscriptionManager) parseEvent(attrs ipp.Attributes) SubscriptionEvent { - event := SubscriptionEvent{ - SubscribedAt: time.Now(), - } - - if attr, ok := attrs["notify-subscribed-event"]; ok && len(attr) > 0 { - if val, ok := attr[0].Value.(string); ok { - event.EventName = val - } - } - - if attr, ok := attrs["printer-name"]; ok && len(attr) > 0 { - if val, ok := attr[0].Value.(string); ok { - event.PrinterName = val - } - } - - if attr, ok := attrs["notify-job-id"]; ok && len(attr) > 0 { - if val, ok := attr[0].Value.(int); ok { - event.JobID = val - } - } - - return event -} - -func (sm *SubscriptionManager) Events() <-chan SubscriptionEvent { - return sm.eventChan -} - -func (sm *SubscriptionManager) 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 - sm.sequenceNumber = 0 - } - - sm.stopChan = make(chan struct{}) -} - -func (sm *SubscriptionManager) 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) - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go deleted file mode 100644 index b205a6b..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/subscription_dbus.go +++ /dev/null @@ -1,295 +0,0 @@ -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]any{ - "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) - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go deleted file mode 100644 index d0203e9..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection.go +++ /dev/null @@ -1,176 +0,0 @@ -package cups - -import ( - "errors" - "fmt" - "net" - "strings" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" -) - -var validProtocols = map[string]bool{ - "ipp": true, - "ipps": true, - "lpd": true, - "socket": true, -} - -func validateTestConnectionParams(host string, port int, protocol string) error { - if host == "" { - return errors.New("host is required") - } - if strings.ContainsAny(host, " \t\n\r/\\") { - return errors.New("host contains invalid characters") - } - if port < 1 || port > 65535 { - return errors.New("port must be between 1 and 65535") - } - if protocol != "" && !validProtocols[protocol] { - return errors.New("protocol must be one of: ipp, ipps, lpd, socket") - } - return nil -} - -const probeTimeout = 10 * time.Second - -func probeRemotePrinter(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - addr := net.JoinHostPort(host, fmt.Sprintf("%d", port)) - - // Fast fail: TCP reachability check - conn, err := net.DialTimeout("tcp", addr, probeTimeout) - if err != nil { - return &RemotePrinterInfo{ - Reachable: false, - Error: fmt.Sprintf("cannot reach %s: %s", addr, err.Error()), - }, nil - } - conn.Close() - - // Create a temporary IPP client pointing at the remote host. - // The TCP dial above provides fast-fail for unreachable hosts. - // The IPP adapter's ResponseHeaderTimeout (90s) bounds stalling servers. - client := ipp.NewIPPClient(host, port, "", "", useTLS) - - // Try /ipp/print first (modern driverless printers), then / (legacy) - info, err := probeIPPEndpoint(client, host, port, useTLS, "/ipp/print") - if err != nil { - // If we got an auth error, the printer exists but requires credentials. - // Report it as reachable with the URI that triggered the auth challenge. - if isAuthError(err) { - proto := "ipp" - if useTLS { - proto = "ipps" - } - return &RemotePrinterInfo{ - Reachable: true, - URI: fmt.Sprintf("%s://%s:%d/ipp/print", proto, host, port), - Info: "authentication required", - }, nil - } - info, err = probeIPPEndpoint(client, host, port, useTLS, "/") - } - if err != nil { - if isAuthError(err) { - proto := "ipp" - if useTLS { - proto = "ipps" - } - return &RemotePrinterInfo{ - Reachable: true, - URI: fmt.Sprintf("%s://%s:%d/", proto, host, port), - Info: "authentication required", - }, nil - } - // TCP reachable but not an IPP printer - return &RemotePrinterInfo{ - Reachable: true, - Error: fmt.Sprintf("host is reachable but does not appear to be an IPP printer: %s", err.Error()), - }, nil - } - - return info, nil -} - -func probeIPPEndpoint(client *ipp.IPPClient, host string, port int, useTLS bool, resourcePath string) (*RemotePrinterInfo, error) { - proto := "ipp" - if useTLS { - proto = "ipps" - } - printerURI := fmt.Sprintf("%s://%s:%d%s", proto, host, port, resourcePath) - - httpProto := "http" - if useTLS { - httpProto = "https" - } - httpURL := fmt.Sprintf("%s://%s:%d%s", httpProto, host, port, resourcePath) - - req := ipp.NewRequest(ipp.OperationGetPrinterAttributes, 1) - req.OperationAttributes[ipp.AttributePrinterURI] = printerURI - req.OperationAttributes[ipp.AttributeRequestedAttributes] = []string{ - ipp.AttributePrinterName, - ipp.AttributePrinterMakeAndModel, - ipp.AttributePrinterState, - ipp.AttributePrinterInfo, - ipp.AttributePrinterUriSupported, - } - - resp, err := client.SendRequest(httpURL, req, nil) - if err != nil { - return nil, err - } - - if len(resp.PrinterAttributes) == 0 { - return nil, errors.New("no printer attributes returned") - } - - attrs := resp.PrinterAttributes[0] - - return &RemotePrinterInfo{ - Reachable: true, - MakeModel: getStringAttr(attrs, ipp.AttributePrinterMakeAndModel), - Name: getStringAttr(attrs, ipp.AttributePrinterName), - Info: getStringAttr(attrs, ipp.AttributePrinterInfo), - State: parsePrinterState(attrs), - URI: printerURI, - }, nil -} - -// TestRemotePrinter validates inputs and probes a remote printer via IPP. -// For lpd/socket protocols, only TCP reachability is tested. -func (m *Manager) TestRemotePrinter(host string, port int, protocol string) (*RemotePrinterInfo, error) { - if protocol == "" { - protocol = "ipp" - } - - if err := validateTestConnectionParams(host, port, protocol); err != nil { - return nil, err - } - - // For non-IPP protocols, only check TCP reachability - if protocol == "lpd" || protocol == "socket" { - addr := net.JoinHostPort(host, fmt.Sprintf("%d", port)) - conn, err := net.DialTimeout("tcp", addr, probeTimeout) - if err != nil { - return &RemotePrinterInfo{ - Reachable: false, - Error: fmt.Sprintf("cannot reach %s: %s", addr, err.Error()), - }, nil - } - conn.Close() - return &RemotePrinterInfo{ - Reachable: true, - URI: fmt.Sprintf("%s://%s:%d", protocol, host, port), - }, nil - } - - useTLS := protocol == "ipps" - - probeFn := m.probeRemoteFn - if probeFn == nil { - probeFn = probeRemotePrinter - } - - return probeFn(host, port, useTLS) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go deleted file mode 100644 index b835028..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/test_connection_test.go +++ /dev/null @@ -1,397 +0,0 @@ -package cups - -import ( - "bytes" - "encoding/json" - "fmt" - "testing" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/stretchr/testify/assert" -) - -func TestValidateTestConnectionParams(t *testing.T) { - tests := []struct { - name string - host string - port int - protocol string - wantErr string - }{ - { - name: "valid ipp", - host: "192.168.0.5", - port: 631, - protocol: "ipp", - wantErr: "", - }, - { - name: "valid ipps", - host: "printer.local", - port: 443, - protocol: "ipps", - wantErr: "", - }, - { - name: "valid lpd", - host: "10.0.0.1", - port: 515, - protocol: "lpd", - wantErr: "", - }, - { - name: "valid socket", - host: "10.0.0.1", - port: 9100, - protocol: "socket", - wantErr: "", - }, - { - name: "empty host", - host: "", - port: 631, - protocol: "ipp", - wantErr: "host is required", - }, - { - name: "port too low", - host: "192.168.0.5", - port: 0, - protocol: "ipp", - wantErr: "port must be between 1 and 65535", - }, - { - name: "port too high", - host: "192.168.0.5", - port: 70000, - protocol: "ipp", - wantErr: "port must be between 1 and 65535", - }, - { - name: "invalid protocol", - host: "192.168.0.5", - port: 631, - protocol: "ftp", - wantErr: "protocol must be one of: ipp, ipps, lpd, socket", - }, - { - name: "empty protocol treated as ipp", - host: "192.168.0.5", - port: 631, - protocol: "", - wantErr: "", - }, - { - name: "host with slash", - host: "192.168.0.5/admin", - port: 631, - protocol: "ipp", - wantErr: "host contains invalid characters", - }, - { - name: "host with space", - host: "192.168.0.5 ", - port: 631, - protocol: "ipp", - wantErr: "host contains invalid characters", - }, - { - name: "host with newline", - host: "192.168.0.5\n", - port: 631, - protocol: "ipp", - wantErr: "host contains invalid characters", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateTestConnectionParams(tt.host, tt.port, tt.protocol) - if tt.wantErr == "" { - assert.NoError(t, err) - } else { - assert.EqualError(t, err, tt.wantErr) - } - }) - } -} - -func TestManager_TestRemotePrinter_Validation(t *testing.T) { - m := NewTestManager(nil, nil) - - tests := []struct { - name string - host string - port int - protocol string - wantErr string - }{ - { - name: "empty host returns error", - host: "", - port: 631, - protocol: "ipp", - wantErr: "host is required", - }, - { - name: "invalid port returns error", - host: "192.168.0.5", - port: 0, - protocol: "ipp", - wantErr: "port must be between 1 and 65535", - }, - { - name: "invalid protocol returns error", - host: "192.168.0.5", - port: 631, - protocol: "ftp", - wantErr: "protocol must be one of: ipp, ipps, lpd, socket", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := m.TestRemotePrinter(tt.host, tt.port, tt.protocol) - assert.EqualError(t, err, tt.wantErr) - }) - } -} - -func TestManager_TestRemotePrinter_IPP(t *testing.T) { - tests := []struct { - name string - protocol string - probeRet *RemotePrinterInfo - probeErr error - wantTLS bool - wantReach bool - wantModel string - }{ - { - name: "successful ipp probe", - protocol: "ipp", - probeRet: &RemotePrinterInfo{ - Reachable: true, - MakeModel: "HP OfficeJet 8010", - Name: "OfficeJet", - State: "idle", - URI: "ipp://192.168.0.5:631/ipp/print", - }, - wantTLS: false, - wantReach: true, - wantModel: "HP OfficeJet 8010", - }, - { - name: "successful ipps probe", - protocol: "ipps", - probeRet: &RemotePrinterInfo{ - Reachable: true, - MakeModel: "HP OfficeJet 8010", - URI: "ipps://192.168.0.5:631/ipp/print", - }, - wantTLS: true, - wantReach: true, - wantModel: "HP OfficeJet 8010", - }, - { - name: "unreachable host", - protocol: "ipp", - probeRet: &RemotePrinterInfo{ - Reachable: false, - Error: "cannot reach 192.168.0.5:631: connection refused", - }, - wantReach: false, - }, - { - name: "empty protocol defaults to ipp", - protocol: "", - probeRet: &RemotePrinterInfo{ - Reachable: true, - MakeModel: "Test Printer", - }, - wantTLS: false, - wantReach: true, - wantModel: "Test Printer", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var capturedTLS bool - m := NewTestManager(nil, nil) - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - capturedTLS = useTLS - return tt.probeRet, tt.probeErr - } - - result, err := m.TestRemotePrinter("192.168.0.5", 631, tt.protocol) - if tt.probeErr != nil { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Equal(t, tt.wantReach, result.Reachable) - assert.Equal(t, tt.wantModel, result.MakeModel) - assert.Equal(t, tt.wantTLS, capturedTLS) - }) - } -} - -func TestManager_TestRemotePrinter_AuthRequired(t *testing.T) { - m := NewTestManager(nil, nil) - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - // Simulate what happens when the printer returns HTTP 401 - return probeRemotePrinterWithAuthError(host, port, useTLS) - } - - result, err := m.TestRemotePrinter("192.168.0.107", 631, "ipp") - assert.NoError(t, err) - assert.True(t, result.Reachable) - assert.Equal(t, "authentication required", result.Info) - assert.Contains(t, result.URI, "ipp://192.168.0.107:631") -} - -// probeRemotePrinterWithAuthError simulates a probe where the printer -// returns HTTP 401 on both endpoints. -func probeRemotePrinterWithAuthError(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - // This simulates what probeRemotePrinter does when both endpoints - // return auth errors. We test the auth detection logic directly. - err := ipp.HTTPError{Code: 401} - if isAuthError(err) { - proto := "ipp" - if useTLS { - proto = "ipps" - } - return &RemotePrinterInfo{ - Reachable: true, - URI: fmt.Sprintf("%s://%s:%d/ipp/print", proto, host, port), - Info: "authentication required", - }, nil - } - return nil, err -} - -func TestManager_TestRemotePrinter_NonIPPProtocol(t *testing.T) { - m := NewTestManager(nil, nil) - probeCalled := false - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - probeCalled = true - return nil, nil - } - - // These will fail at TCP dial (no real server), but the important - // thing is that probeRemoteFn is NOT called for lpd/socket. - m.TestRemotePrinter("192.168.0.5", 9100, "socket") - assert.False(t, probeCalled, "probe function should not be called for socket protocol") - - m.TestRemotePrinter("192.168.0.5", 515, "lpd") - assert.False(t, probeCalled, "probe function should not be called for lpd protocol") -} - -func TestHandleTestConnection_Success(t *testing.T) { - m := NewTestManager(nil, nil) - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - return &RemotePrinterInfo{ - Reachable: true, - MakeModel: "HP OfficeJet 8010", - Name: "OfficeJet", - State: "idle", - URI: "ipp://192.168.0.5:631/ipp/print", - }, nil - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.testConnection", - Params: map[string]any{ - "host": "192.168.0.5", - }, - } - - handleTestConnection(conn, req, m) - - var resp models.Response[RemotePrinterInfo] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Reachable) - assert.Equal(t, "HP OfficeJet 8010", resp.Result.MakeModel) -} - -func TestHandleTestConnection_MissingHost(t *testing.T) { - m := NewTestManager(nil, nil) - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.testConnection", - Params: map[string]any{}, - } - - handleTestConnection(conn, req, m) - - var resp models.Response[any] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.Nil(t, resp.Result) - assert.NotNil(t, resp.Error) -} - -func TestHandleTestConnection_CustomPortAndProtocol(t *testing.T) { - m := NewTestManager(nil, nil) - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - assert.Equal(t, 9631, port) - assert.True(t, useTLS) - return &RemotePrinterInfo{Reachable: true, URI: "ipps://192.168.0.5:9631/ipp/print"}, nil - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.testConnection", - Params: map[string]any{ - "host": "192.168.0.5", - "port": float64(9631), - "protocol": "ipps", - }, - } - - handleTestConnection(conn, req, m) - - var resp models.Response[RemotePrinterInfo] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Reachable) -} - -func TestHandleRequest_TestConnection(t *testing.T) { - m := NewTestManager(nil, nil) - m.probeRemoteFn = func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) { - return &RemotePrinterInfo{Reachable: true}, nil - } - - buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} - - req := models.Request{ - ID: 1, - Method: "cups.testConnection", - Params: map[string]any{"host": "192.168.0.5"}, - } - - HandleRequest(conn, req, m) - - var resp models.Response[RemotePrinterInfo] - err := json.NewDecoder(buf).Decode(&resp) - assert.NoError(t, err) - assert.NotNil(t, resp.Result) - assert.True(t, resp.Result.Reachable) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go deleted file mode 100644 index 4820e34..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/testing.go +++ /dev/null @@ -1,13 +0,0 @@ -package cups - -func NewTestManager(client CUPSClientInterface, pkHelper PkHelper) *Manager { - return &Manager{ - client: client, - pkHelper: pkHelper, - state: &CUPSState{ - Printers: make(map[string]*Printer), - }, - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go deleted file mode 100644 index 52e9d2c..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/cups/types.go +++ /dev/null @@ -1,132 +0,0 @@ -package cups - -import ( - "io" - "sync" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" -) - -type CUPSState struct { - Printers map[string]*Printer `json:"printers"` -} - -type Printer struct { - Name string `json:"name"` - URI string `json:"uri"` - State string `json:"state"` - StateReason string `json:"stateReason"` - Location string `json:"location"` - Info string `json:"info"` - MakeModel string `json:"makeModel"` - Accepting bool `json:"accepting"` - Jobs []Job `json:"jobs"` -} - -type Job struct { - ID int `json:"id"` - Name string `json:"name"` - State string `json:"state"` - Printer string `json:"printer"` - User string `json:"user"` - Size int `json:"size"` - TimeCreated time.Time `json:"timeCreated"` -} - -type Device struct { - URI string `json:"uri"` - Class string `json:"class"` - Info string `json:"info"` - MakeModel string `json:"makeModel"` - ID string `json:"id"` - Location string `json:"location"` - IP string `json:"ip,omitempty"` -} - -type PPD struct { - Name string `json:"name"` - NaturalLanguage string `json:"naturalLanguage"` - MakeModel string `json:"makeModel"` - DeviceID string `json:"deviceId"` - Product string `json:"product"` - PSVersion string `json:"psVersion"` - Type string `json:"type"` -} - -type RemotePrinterInfo struct { - Reachable bool `json:"reachable"` - MakeModel string `json:"makeModel"` - Name string `json:"name"` - Info string `json:"info"` - State string `json:"state"` - URI string `json:"uri"` - Error string `json:"error,omitempty"` -} - -type PrinterClass struct { - Name string `json:"name"` - URI string `json:"uri"` - State string `json:"state"` - Members []string `json:"members"` - Location string `json:"location"` - Info string `json:"info"` -} - -type Manager struct { - state *CUPSState - client CUPSClientInterface - pkHelper PkHelper - subscription SubscriptionManagerInterface - stateMutex sync.RWMutex - subscribers syncmap.Map[string, chan CUPSState] - stopChan chan struct{} - eventWG sync.WaitGroup - dirty chan struct{} - notifierWg sync.WaitGroup - lastNotifiedState *CUPSState - baseURL string - probeRemoteFn func(host string, port int, useTLS bool) (*RemotePrinterInfo, error) -} - -type SubscriptionManagerInterface interface { - Start() error - Stop() - Events() <-chan SubscriptionEvent -} - -type CUPSClientInterface interface { - GetPrinters(attributes []string) (map[string]ipp.Attributes, error) - GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]ipp.Attributes, error) - CancelJob(jobID int, purge bool) error - PausePrinter(printer string) error - ResumePrinter(printer string) error - CancelAllJob(printer string, purge bool) error - SendRequest(url string, req *ipp.Request, additionalResponseData io.Writer) (*ipp.Response, error) - - GetDevices() (map[string]ipp.Attributes, error) - GetPPDs() (map[string]ipp.Attributes, error) - GetClasses(attributes []string) (map[string]ipp.Attributes, error) - CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error - DeletePrinter(printer string) error - AcceptJobs(printer string) error - RejectJobs(printer string) error - SetPrinterIsShared(printer string, shared bool) error - SetPrinterLocation(printer, location string) error - SetPrinterInformation(printer, information string) error - MoveJob(jobID int, destPrinter string) error - PrintTestPage(printer string, testPageData io.Reader, size int) (int, error) - AddPrinterToClass(class, printer string) error - DeletePrinterFromClass(class, printer string) error - DeleteClass(class string) error - RestartJob(jobID int) error - HoldJobUntil(jobID int, holdUntil string) error -} - -type SubscriptionEvent struct { - EventName string - PrinterName string - JobID int - SubscribedAt time.Time -} |