diff --git a/attribute.go b/attribute.go index 383a033..0792efe 100644 --- a/attribute.go +++ b/attribute.go @@ -11,18 +11,17 @@ const ( sizeBoolean = int16(1) ) -// ipp attribute encoder -// encodes attribute to a io.Writer +// AttributeEncoder encodes attribute to a io.Writer type AttributeEncoder struct { writer io.Writer } -// create a new attribute encoder from a writer +// NewAttributeEncoder returns a new encoder that writes to w func NewAttributeEncoder(w io.Writer) *AttributeEncoder { return &AttributeEncoder{w} } -// encodes a attribute and its value to a io.Writer +// Encode encodes a attribute and its value to a io.Writer // the tag is determined by the AttributeTagMapping map func (e *AttributeEncoder) Encode(attribute string, value interface{}) error { tag, ok := AttributeTagMapping[attribute] @@ -343,27 +342,31 @@ func (e *AttributeEncoder) writeNullByte() error { return binary.Write(e.writer, binary.BigEndian, int16(0)) } -// representation of a ipp attribute -// a attribute contains a tag, witch identifies the type, the name of the attribute a the value +// Attribute defines an ipp attribute type Attribute struct { Tag int8 Name string Value interface{} } -// ipp attribute decoder -// reads from a io.Reader an decode the data into an attribute struct +// Resolution defines the resolution attribute +type Resolution struct { + Height int + Width int + Depth int8 +} + +// AttributeDecoder reads and decodes ipp from an input stream type AttributeDecoder struct { reader io.Reader } -// create a new attribute decoder from a reader +// NewAttributeDecoder returns a new decoder that reads from r func NewAttributeDecoder(r io.Reader) *AttributeDecoder { return &AttributeDecoder{r} } -// reads from a io.Reader and decode the attribute -// the type is identified by a tag passed as an argument +// Decode reads the next ipp attribute into a attribute struct. the type is identified by a tag passed as an argument func (d *AttributeDecoder) Decode(tag int8) (*Attribute, error) { attr := Attribute{Tag: tag} @@ -498,13 +501,6 @@ func (d *AttributeDecoder) decodeRange() ([]int, error) { return r, nil } -// represents the data of the resolution attribute -type Resolution struct { - Height int - Width int - Depth int8 -} - func (d *AttributeDecoder) decodeResolution() (res Resolution, err error) { _, err = d.readValueLength() if err != nil { diff --git a/constants.go b/constants.go index 02ebfd6..3e2e7f7 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,6 @@ package ipp +// ipp status codes const ( StatusCupsInvalid int16 = -1 StatusOk int16 = 0x0000 @@ -66,6 +67,7 @@ const ( StatusErrorCupsUpgradeRequired int16 = 0x1002 ) +// ipp operations const ( OperationCupsInvalid int16 = -0x0001 OperationCupsNone int16 = 0x0000 @@ -186,6 +188,7 @@ const ( OperationCupsCreateLocalPrinter int16 = 0x4028 ) +// ipp tags const ( TagCupsInvalid int8 = -1 TagZero int8 = 0x00 @@ -230,6 +233,7 @@ const ( TagExtension int8 = 0x7f ) +// job states const ( JobStatePending int8 = 0x03 JobStateHeld int8 = 0x04 @@ -240,6 +244,7 @@ const ( JobStateCompleted int8 = 0x09 ) +// document states const ( DocumentStatePending int8 = 0x03 DocumentStateProcessing int8 = 0x05 @@ -248,18 +253,21 @@ const ( DocumentStateCompleted int8 = 0x08 ) +// printer states const ( PrinterStateIdle int8 = 0x0003 PrinterStateProcessing int8 = 0x0004 PrinterStateStopped int8 = 0x0005 ) +// job state filter const ( JobStateFilterNotCompleted = "not-completed" JobStateFilterCompleted = "completed" JobStateFilterAll = "all" ) +// error policies const ( ErrorPolicyRetryJob = "retry-job" ErrorPolicyAbortJob = "abort-job" @@ -267,6 +275,7 @@ const ( ErrorPolicyStopPrinter = "stop-printer" ) +// ipp defaults const ( CharsetLanguage = "en-US" Charset = "utf-8" @@ -274,13 +283,20 @@ const ( ProtocolVersionMinor = int8(0) DefaultJobPriority = 50 +) +// useful mime types for ipp +const ( MimeTypePostscript = "application/postscript" MimeTypeOctetStream = "application/octet-stream" +) +// ipp content types +const ( ContentTypeIPP = "application/ipp" ) +// known ipp attributes const ( AttributeCopies = "copies" AttributeDocumentFormat = "document-format" @@ -337,6 +353,7 @@ const ( AttributeJobOriginatingUserName = "job-originating-user-name" ) +// Default attributes var ( DefaultClassAttributes = []string{AttributePrinterName, AttributeMemberNames} DefaultPrinterAttributes = []string{AttributePrinterName, AttributePrinterType, AttributePrinterLocation, AttributePrinterInfo, @@ -345,7 +362,10 @@ var ( DefaultJobAttributes = []string{AttributeJobID, AttributeJobName, AttributePrinterURI, AttributeJobState, AttributeJobStateReason, AttributeJobHoldUntil, AttributeJobMediaProgress, AttributeJobKilobyteOctets, AttributeNumberOfDocuments, AttributeCopies, AttributeJobOriginatingUserName} +) +// Attribute to tag mapping +var ( AttributeTagMapping = map[string]int8{ AttributeCharset: TagCharset, AttributeNaturalLanguage: TagLanguage, diff --git a/error.go b/error.go index 240f791..e4edf5e 100644 --- a/error.go +++ b/error.go @@ -2,7 +2,7 @@ package ipp import "fmt" -// check a given error whether a printer or class does not exist +// IsNotExistsError checks a given error whether a printer or class does not exist func IsNotExistsError(err error) bool { if err == nil { return false @@ -11,7 +11,7 @@ func IsNotExistsError(err error) bool { return err.Error() == "The printer or class does not exist." } -//non ok ipp status codes +// IPPError used for non ok ipp status codes type IPPError struct { Status int16 Message string @@ -21,7 +21,7 @@ func (e IPPError) Error() string { return fmt.Sprintf("ipp status: %d, message: %s", e.Status, e.Message) } -// non 200 http codes +// HTTPError used for non 200 http codes type HTTPError struct { Code int } diff --git a/ipp-client.go b/ipp-client.go index b4b5387..7b68937 100644 --- a/ipp-client.go +++ b/ipp-client.go @@ -13,6 +13,7 @@ import ( "strconv" ) +// Document wraps an io.Reader with more information, needed for encoding type Document struct { Document io.Reader Size int @@ -20,6 +21,7 @@ type Document struct { MimeType string } +// IPPClient implements a generic ipp client type IPPClient struct { host string port int @@ -30,6 +32,7 @@ type IPPClient struct { client *http.Client } +// NewIPPClient creates a new generic ipp client func NewIPPClient(host string, port int, username, password string, useTLS bool) *IPPClient { httpClient := http.Client{ Transport: &http.Transport{ @@ -73,6 +76,7 @@ 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) { payload, err := req.Encode() if err != nil { @@ -123,7 +127,7 @@ func (c *IPPClient) SendRequest(url string, req *Request, additionalResponseData return resp, err } -// Print one or more `Document`s using IPP `Create-Job` followed by `Send-Document` request(s). +// 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) @@ -173,23 +177,7 @@ func (c *IPPClient) PrintDocuments(docs []Document, printer string, jobAttribute return jobID, nil } -// Print a `Document` using an IPP `Print-Job` request. -// -// `jobAttributes` can contain arbitrary key/value pairs to control the way in which the -// document is printed. [RFC 2911 ยง 4.2](https://tools.ietf.org/html/rfc2911#section-4.2) -// defines some useful attributes: -// -// * [`job-priority`](https://tools.ietf.org/html/rfc2911#section-4.2.1): an integer between 1-100 -// * [`copies`](https://tools.ietf.org/html/rfc2911#section-4.2.5): a positive integer -// * [`finishings`](https://tools.ietf.org/html/rfc2911#section-4.2.6): an enumeration -// * [`number-up`](https://tools.ietf.org/html/rfc2911#section-4.2.9): a positive integer -// * [`orientation-requested`](https://tools.ietf.org/html/rfc2911#section-4.2.10): an enumeration -// * [`media`](https://tools.ietf.org/html/rfc2911#section-4.2.11): a string -// * [`printer-resolution`](https://tools.ietf.org/html/rfc2911#section-4.2.12): a `Resolution` -// * [`print-quality`](https://tools.ietf.org/html/rfc2911#section-4.2.13): an enumeration -// -// Your print system may provide other attributes. Define custom attributes as needed in -// `AttributeTagMapping` and provide values here. +// 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) @@ -224,6 +212,7 @@ func (c *IPPClient) PrintJob(doc Document, printer string, jobAttributes map[str 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) { @@ -250,6 +239,7 @@ func (c *IPPClient) PrintFile(filePath, printer string, jobAttributes map[string }, 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) @@ -273,6 +263,7 @@ func (c *IPPClient) GetPrinterAttributes(printer string, attributes []string) (A 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) @@ -281,6 +272,7 @@ func (c *IPPClient) ResumePrinter(printer string) error { return err } +// PausePrinter pauses a printer func (c *IPPClient) PausePrinter(printer string) error { req := NewRequest(OperationPausePrinter, 1) req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer) @@ -289,6 +281,7 @@ func (c *IPPClient) PausePrinter(printer string) error { 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) @@ -311,6 +304,7 @@ func (c *IPPClient) GetJobAttributes(jobID int, attributes []string) (Attributes return resp.PrinterAttributes[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) @@ -356,6 +350,7 @@ func (c *IPPClient) GetJobs(printer, class string, whichJobs string, myJobs bool 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) @@ -365,6 +360,7 @@ func (c *IPPClient) CancelJob(jobID int, purge bool) error { return err } +// CancelJob 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) @@ -374,6 +370,7 @@ func (c *IPPClient) CancelAllJob(printer string, purge bool) error { return err } +// RestartJob restarts a job func (c *IPPClient) RestartJob(jobID int) error { req := NewRequest(OperationRestartJob, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) @@ -382,6 +379,7 @@ func (c *IPPClient) RestartJob(jobID int) error { return err } +// RestartJob holds a job func (c *IPPClient) HoldJobUntil(jobID int, holdUntil string) error { req := NewRequest(OperationRestartJob, 1) req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID) @@ -391,6 +389,7 @@ func (c *IPPClient) HoldJobUntil(jobID int, holdUntil string) error { return err } +// PrintTestPage prints a test page of type application/vnd.cups-pdf-banner func (c *IPPClient) PrintTestPage(printer string) (int, error) { testPage := new(bytes.Buffer) testPage.WriteString("#PDF-BANNER\n") @@ -411,6 +410,7 @@ func (c *IPPClient) PrintTestPage(printer string) (int, error) { }) } +// TestConnection tests if a tcp connection to the remote server is possible func (c *IPPClient) TestConnection() error { conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.host, c.port)) if err != nil { diff --git a/reqest.go b/reqest.go index 3deee77..f0244a7 100644 --- a/reqest.go +++ b/reqest.go @@ -6,6 +6,7 @@ import ( "io" ) +// Request defines a ipp request type Request struct { ProtocolVersionMajor int8 ProtocolVersionMinor int8 @@ -21,6 +22,7 @@ type Request struct { FileSize int } +// NewRequest creates a new ipp request func NewRequest(op int16, reqID int32) *Request { return &Request{ ProtocolVersionMajor: ProtocolVersionMajor, @@ -35,6 +37,7 @@ func NewRequest(op int16, reqID int32) *Request { } } +// Encode encodes the request to a byte slice func (r *Request) Encode() ([]byte, error) { buf := new(bytes.Buffer) enc := NewAttributeEncoder(buf) @@ -104,16 +107,19 @@ func (r *Request) Encode() ([]byte, error) { return buf.Bytes(), nil } +// RequestDecoder reads and decodes a request from a stream type RequestDecoder struct { reader io.Reader } +// NewRequestDecoder returns a new decoder that reads from r func NewRequestDecoder(r io.Reader) *RequestDecoder { return &RequestDecoder{ reader: r, } } +// Decode decodes a ipp request into a request struct. additional data will be written to an io.Writer if data is not nil func (d *RequestDecoder) Decode(data io.Writer) (*Request, error) { req := new(Request) diff --git a/response.go b/response.go index 6424ba5..eb61a3a 100644 --- a/response.go +++ b/response.go @@ -7,8 +7,10 @@ import ( "io" ) +// Attributes is a wrapper for a set of attributes type Attributes map[string][]Attribute +// Response defines a ipp response type Response struct { ProtocolVersionMajor int8 ProtocolVersionMinor int8 @@ -21,6 +23,7 @@ type Response struct { JobAttributes []Attributes } +// CheckForErrors checks the status code and returns a error if it is not zero. it also returns the status message if provided by the server func (r *Response) CheckForErrors() error { if r.StatusCode != StatusOk { err := IPPError{ @@ -38,6 +41,7 @@ func (r *Response) CheckForErrors() error { return nil } +// NewRequest creates a new ipp response func NewResponse(statusCode int16, reqID int32) *Response { return &Response{ ProtocolVersionMajor: ProtocolVersionMajor, @@ -50,7 +54,8 @@ func NewResponse(statusCode int16, reqID int32) *Response { } } -func (r *Response) Encode(data io.Writer) ([]byte, error) { +// Encode encodes the response to a byte slice +func (r *Response) Encode() ([]byte, error) { buf := new(bytes.Buffer) enc := NewAttributeEncoder(buf) @@ -163,23 +168,26 @@ func (r *Response) Encode(data io.Writer) ([]byte, error) { } } - if err := binary.Write(buf, binary.BigEndian, int8(TagEnd)); err != nil { + if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil { return nil, err } return buf.Bytes(), nil } +// ResponseDecoder reads and decodes a response from a stream type ResponseDecoder struct { reader io.Reader } +// NewResponseDecoder returns a new decoder that reads from r func NewResponseDecoder(r io.Reader) *ResponseDecoder { return &ResponseDecoder{ reader: r, } } +// Decode decodes a ipp response into a response struct. additional data will be written to an io.Writer if data is not nil func (d *ResponseDecoder) Decode(data io.Writer) (*Response, error) { /* 1 byte: Protocol Major Version - b