/* ** Copyright 2022 Dolysis Consulting Limited ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. ** ** For changes see the git log */ package ipp import ( "errors" "fmt" "io" "os" "path" ) // Document wraps an io.Reader with more information, needed for encoding type Document struct { Document io.Reader Size int Name string MimeType string } // IPPClient implements a generic ipp client type IPPClient struct { username string adapter Adapter } // NewIPPClient creates a new generic ipp client (used HttpAdapter internally) func NewIPPClient(host string, port int, username, password string, useTLS bool) *IPPClient { adapter := NewHttpAdapter(host, port, username, password, useTLS) return &IPPClient{ username: username, adapter: adapter, } } // NewIPPClientWithAdapter creates a new generic ipp client with given Adapter func NewIPPClientWithAdapter(username string, adapter Adapter) *IPPClient { return &IPPClient{ username: username, adapter: adapter, } } func (c *IPPClient) getPrinterUri(printer string) string { return fmt.Sprintf("ipp://localhost/printers/%s", printer) } func (c *IPPClient) getJobUri(jobID int) string { return fmt.Sprintf("ipp://localhost/jobs/%d", jobID) } func (c *IPPClient) getClassUri(printer string) string { return fmt.Sprintf("ipp://localhost/classes/%s", printer) } // SendRequest sends a request to a remote uri end returns the response func (c *IPPClient) SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) { if _, ok := req.OperationAttributes[AttributeRequestingUserName]; ok == false { req.OperationAttributes[AttributeRequestingUserName] = c.username } return c.adapter.SendRequest(url, req, additionalResponseData) } // PrintDocuments prints one or more documents using a Create-Job operation followed by one or more Send-Document operation(s). custom job settings can be specified via the jobAttributes parameter func (c *IPPClient) PrintDocuments(docs []Document, printer string, jobAttributes map[string]interface{}) (int, error) { printerURI := c.getPrinterUri(printer) req := NewRequest(OperationCreateJob, 1) req.OperationAttributes[AttributePrinterURI] = printerURI req.OperationAttributes[AttributeRequestingUserName] = c.username // set defaults for some attributes, may get overwritten req.OperationAttributes[AttributeJobName] = docs[0].Name req.OperationAttributes[AttributeCopies] = 1 req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority for key, value := range jobAttributes { req.JobAttributes[key] = value } resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) if err != nil { return -1, err } if len(resp.JobAttributes) == 0 { return 0, errors.New("server doesn't returned a job id") } jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int) documentCount := len(docs) - 1 for docID, doc := range docs { req = NewRequest(OperationSendDocument, 2) req.OperationAttributes[AttributePrinterURI] = printerURI req.OperationAttributes[AttributeRequestingUserName] = c.username req.OperationAttributes[AttributeJobID] = jobID req.OperationAttributes[AttributeDocumentName] = doc.Name req.OperationAttributes[AttributeDocumentFormat] = doc.MimeType req.OperationAttributes[AttributeLastDocument] = docID == documentCount req.File = doc.Document req.FileSize = doc.Size _, err = c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) if err != nil { return -1, err } } return jobID, nil } // PrintJob prints a document using a Print-Job operation. custom job settings can be specified via the jobAttributes parameter func (c *IPPClient) PrintJob(doc Document, printer string, jobAttributes map[string]interface{}) (int, error) { printerURI := c.getPrinterUri(printer) req := NewRequest(OperationPrintJob, 1) req.OperationAttributes[AttributePrinterURI] = printerURI req.OperationAttributes[AttributeRequestingUserName] = c.username req.OperationAttributes[AttributeJobName] = doc.Name req.OperationAttributes[AttributeDocumentFormat] = doc.MimeType // set defaults for some attributes, may get overwritten req.OperationAttributes[AttributeCopies] = 1 req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority for key, value := range jobAttributes { req.JobAttributes[key] = value } req.File = doc.Document req.FileSize = doc.Size resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) if err != nil { return -1, err } if len(resp.JobAttributes) == 0 { return 0, errors.New("server doesn't returned a job id") } jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int) return jobID, nil } // PrintFile prints a local file on the file system. custom job settings can be specified via the jobAttributes parameter func (c *IPPClient) PrintFile(filePath, printer string, jobAttributes map[string]interface{}) (int, error) { fileStats, err := os.Stat(filePath) if os.IsNotExist(err) { return -1, err } fileName := path.Base(filePath) document, err := os.Open(filePath) if err != nil { return 0, err } defer document.Close() jobAttributes[AttributeJobName] = fileName return c.PrintDocuments([]Document{ { Document: document, Name: fileName, Size: int(fileStats.Size()), MimeType: MimeTypeOctetStream, }, }, printer, jobAttributes) } // GetPrinterAttributes returns the requested attributes for the specified printer, if attributes is nil the default attributes will be used func (c *IPPClient) GetPrinterAttributes(printer string, attributes []string) (Attributes, error) { req := NewRequest(OperationGetPrinterAttributes, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) req.OperationAttributes[AttributeRequestingUserName] = c.username if attributes == nil { req.OperationAttributes[AttributeRequestedAttributes] = DefaultPrinterAttributes } else { req.OperationAttributes[AttributeRequestedAttributes] = attributes } resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil) if err != nil { return nil, err } if len(resp.PrinterAttributes) == 0 { return nil, errors.New("server doesn't return any printer attributes") } return resp.PrinterAttributes[0], nil } // ResumePrinter resumes a printer func (c *IPPClient) ResumePrinter(printer string) error { req := NewRequest(OperationResumePrinter, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) return err } // PausePrinter pauses a printer func (c *IPPClient) PausePrinter(printer string) error { req := NewRequest(OperationPausePrinter, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) return err } // GetJobAttributes returns the requested attributes for the specified job, if attributes is nil the default job will be used func (c *IPPClient) GetJobAttributes(jobID int, attributes []string) (Attributes, error) { req := NewRequest(OperationGetJobAttributes, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) if attributes == nil { req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes } else { req.OperationAttributes[AttributeRequestedAttributes] = attributes } resp, err := c.SendRequest(c.adapter.GetHttpUri("jobs", jobID), req, nil) if err != nil { return nil, err } if len(resp.JobAttributes) == 0 { return nil, errors.New("server doesn't return any job attributes") } return resp.JobAttributes[0], nil } // GetJobs returns jobs from a printer or class func (c *IPPClient) GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]Attributes, error) { req := NewRequest(OperationGetJobs, 1) req.OperationAttributes[AttributeWhichJobs] = string(whichJobs) req.OperationAttributes[AttributeMyJobs] = myJobs if printer != "" { req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) } else if class != "" { req.OperationAttributes[AttributePrinterURI] = c.getClassUri(printer) } else { req.OperationAttributes[AttributePrinterURI] = "ipp://localhost/" } if firstJobId > 0 { req.OperationAttributes[AttributeFirstJobID] = firstJobId } if limit > 0 { req.OperationAttributes[AttributeLimit] = limit } if myJobs { req.OperationAttributes[AttributeRequestingUserName] = c.username } if attributes == nil { req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes } else { req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributeJobID) } resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil) if err != nil { return nil, err } jobIDMap := make(map[int]Attributes) for _, jobAttributes := range resp.JobAttributes { jobIDMap[jobAttributes[AttributeJobID][0].Value.(int)] = jobAttributes } return jobIDMap, nil } // CancelJob cancels a job. if purge is true, the job will also be removed func (c *IPPClient) CancelJob(jobID int, purge bool) error { req := NewRequest(OperationCancelJob, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) req.OperationAttributes[AttributePurgeJobs] = purge _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) return err } // CancelAllJob cancels all jobs for a specified printer. if purge is true, the jobs will also be removed func (c *IPPClient) CancelAllJob(printer string, purge bool) error { req := NewRequest(OperationCancelJobs, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) req.OperationAttributes[AttributePurgeJobs] = purge _, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil) return err } // RestartJob restarts a job func (c *IPPClient) RestartJob(jobID int) error { req := NewRequest(OperationRestartJob, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) return err } // HoldJobUntil holds a job func (c *IPPClient) HoldJobUntil(jobID int, holdUntil string) error { req := NewRequest(OperationRestartJob, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) req.JobAttributes[AttributeHoldJobUntil] = holdUntil _, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil) return err } // TestConnection tests if a tcp connection to the remote server is possible func (c *IPPClient) TestConnection() error { return c.adapter.TestConnection() }