package cups import ( "errors" "net" "net/url" "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 (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 { return err } 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 { 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 }