Skip to content

Commit

Permalink
Explanation about IppRegistration and concept.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmuth committed Oct 16, 2023
1 parent 28a91e8 commit 52267bd
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 22 deletions.
67 changes: 55 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ implementation("de.gmuth:ipp-client:3.0.1")
```

[README.md for version 2.x](https://github.com/gmuth/ipp-client-kotlin/blob/2.5/README.md) is still available.

### [IppPrinter](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt) and [IppJob](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt)

```kotlin
// initialize printer connection and show printer attributes
// Initialize printer connection and show printer attributes
val ippPrinter = IppPrinter(URI.create("ipp://colorjet.local/ipp/printer"))
ippPrinter.attributes.log(logger)

// marker levels
// Marker levels
ippPrinter.markers.forEach { println(it) }
println("black: ${ippPrinter.marker(BLACK).levelPercent()} %")

// print file
// Print file
val file = File("A4-ten-pages.pdf")
val job = ippPrinter.printJob(
file,
Expand All @@ -56,16 +57,16 @@ val job = ippPrinter.printJob(
)
job.subscription?.pollAndHandleNotifications { println(it) }

// print remote file, make printer pull document from remote server
// Print remote file, make printer pull document from remote server
val remoteFile = URI.create("http://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
ippPrinter.printUri(remoteFile)

// create job and send document
// Create job and send document
val job = ippPrinter.createJob(jobName(file.name))
job.sendDocument(FileInputStream(file))
job.waitForTermination()

// manage jobs
// Manage jobs
ippPrinter.getJobs().forEach { println(it) }
ippPrinter.getJobs(WhichJobs.Completed)

Expand All @@ -75,14 +76,14 @@ job.release()
job.cancel()
job.cupsGetDocuments() // CUPS only

// print operator
// Print operator
ippPrinter.pause()
ippPrinter.resume()
ippPrinter.sound() // identify printer

// subscribe and handle/log events (e.g. from CUPS) for 5 minutes
// Subscribe and handle/log events (e.g. from CUPS) for 5 minutes
ippPrinter
.createPrinterSubscription(notifyLeaseDuration=Duration.ofMinutes(5))
.createPrinterSubscription(notifyLeaseDuration = Duration.ofMinutes(5))
.pollAndHandleNotifications()
```

Expand All @@ -95,6 +96,15 @@ WARN: according to printer attributes value 'application/pcl' is not supported.
document-format-supported (1setOf mimeMediaType) = application/pdf,application/postscript
```

To get to know a new printer and its supported features, you can run the inspection workflow
of [IppInspector](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt).
IPP traffic is saved to directory `inspected-printers`. The workflow will try to print a PDF.

```
// need an IPP server? https://openprinting.github.io/cups/doc/man-ippeveprinter.html
IppInspector.inspect(URI.create("ipp://ippeveprinter:8501/ipp/print"))
```

### Exchange [IppRequest](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt) for [IppResponse](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt)

```kotlin
Expand Down Expand Up @@ -172,7 +182,38 @@ printer.printJob(
)
```

## Logging
### IANA Registrations

[Section 2](https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-2)
and [6 registrations](https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6)
are available as Maps and can be queried:

```kotlin
// List media attributes and show syntax
IppRegistrationsSection2.attributesMap.values
.filter { it.name.contains("media") }
.sortedBy { it.collection }
.forEach { println(it) }

// Lookup tag for attribute job-name
IppRegistrationsSection2.tagForAttribute("job-name") // IppTag.NameWithoutLanguage
```

It's not recommended to use IppRegistrations on the happy path of your control flow.
You should rather e.g. lookup the correct tag during development and then use it in your code.
Only when the library detects IPP issues through response codes, it consults the IppRegistrations to help identifying the issue.

This library implements a different concept then jipp.
jipp seems very strict about IPP syntax and is not designed to cope with illegal IPP responses.
My IPP library in contrast is designed for resilience. E.g. it accepts messages and attributes that use wrong IPP tags.
I've not yet seen any IPP server implementation without a single encoding bug.
[IppInputStream](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt)
for example includes workarounds for
[illegal responses of my HP and Xerox printers](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt).
From my experience this approach works better in real life projects than blaming the manufacturers firmware.


### Logging

From version 3.0 onwards the library
uses [Java Logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you
Expand Down Expand Up @@ -215,14 +256,16 @@ contains the
and implementations of higher level IPP objects like
[IppPrinter]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt)),
[IppJob]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt)),
[IppSubscription]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt)) and
[IppSubscription]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt))
and
[IppEventNotification]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt)).

IPP is based on the exchange of binary messages via HTTP.
For reading and writing binary data
[DataInputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataInputStream.html)
and [DataOutputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataOutputStream.html) are
used. For message transportation IppClient uses [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html).
used. For message transportation IppClient
uses [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html).

Only Java runtimes (including Android) provide implementations of these classes.
The Java standard libraries also
Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/de/gmuth/ipp/attributes/MediaCollection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ class MediaCollection(
val type: String? = null
) : IppAttributeBuilder {

override fun buildIppAttribute(printerAttributes: IppAttributesGroup) =
IppAttribute("media-col", BegCollection, IppCollection().apply {
override fun buildIppAttribute(printerAttributes: IppAttributesGroup): IppAttribute<*> {
val mediaSize = size // conflict with IppCollection.size
return IppAttribute("media-col", BegCollection, IppCollection().apply {
type?.let { addAttribute("media-type", Keyword, it) }
size?.let { add(it.buildIppAttribute(printerAttributes)) }
mediaSize?.let { add(it.buildIppAttribute(printerAttributes)) }
margin?.let { addAll(it.buildIppAttributes()) }
source?.let { add(it.buildIppAttribute(printerAttributes)) }
})

}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ object IppInspector {
// URIs
log.info { "Communication channels supported:" }
communicationChannelsSupported.forEach { log.info { " $it" } }
log.info { "Document formats: $documentFormatSupported" }
}

val pdfResource = when {
Expand Down Expand Up @@ -120,6 +121,7 @@ object IppInspector {
release()
}

//updateAttributes("job-state")
if (cancelJob) {
log.info { "> Cancel job" }
cancel()
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ data class IppCollection(val members: MutableCollection<IppAttribute<*>> = mutab
fun <T> getMember(memberName: String) =
members.single { it.name == memberName } as IppAttribute<T>

val size: Int
get() = members.size

override fun toString() = members.joinToString(" ", "{", "}") {
"${it.name}=${it.values.joinToString(",")}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object IppRegistrationsSection2 {
fun getAttribute(name: String, resolveAlias: Boolean = true) =
attributesMap[if (resolveAlias) resolveAlias(name) else name]

fun syntaxForAttribute(name: String, resolveAlias: Boolean) =
fun syntaxForAttribute(name: String, resolveAlias: Boolean = true) =
getAttribute(name, resolveAlias)?.syntax

fun tagForAttribute(name: String) = getAttribute(name)?.tag()
Expand Down
18 changes: 13 additions & 5 deletions src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package de.gmuth.ipp.core
* Copyright (c) 2020-2023 Gerhard Muth
*/

import de.gmuth.log.Logging
import java.io.File
import java.net.URI
import java.util.logging.Logger.getLogger
Expand All @@ -13,6 +14,10 @@ import kotlin.test.assertTrue

class IppResponseTests {

init {
Logging.configure()
}

val log = getLogger(javaClass.name)
private val ippResponse = IppResponse()

Expand All @@ -36,15 +41,18 @@ class IppResponseTests {
}

@Test
fun invalidXeroxMediaColResponse() {
ippResponse.read(File("src/test/resources/invalidXeroxMediaCol.response"))
ippResponse.log(log)
with(ippResponse.jobGroup) {
fun invalidXeroxMediaColResponse() = ippResponse.run {
read(File("src/test/resources/invalidXeroxMediaCol.response"))
log(log)
jobGroup.run {
assertEquals(598, getValue("job-id"))
assertEquals(4, getValue("job-state")) // pending-held
assertEquals(listOf("job-hold-until-specified"), getValues("job-state-reasons"))
assertEquals(URI.create("ipp://xero.local./ipp/print/Job-598"), getValue("job-uri"))
}
unsupportedGroup.run {
assertEquals(0, getValue<IppCollection>("media-col").size)
}
}

@Test
Expand All @@ -53,7 +61,7 @@ class IppResponseTests {
// requestNaturalLanguage = "de" // triggers HP name with language bug
ippResponse.read(File("src/test/resources/invalidHpNameWithLanguage.response"))
ippResponse.log(log)
with(ippResponse.jobGroup) {
ippResponse.jobGroup.run {
assertEquals(IppString("A4-blank.pdf", "de"), getValue("job-name"))
assertEquals(993, getValue("job-id"))
assertEquals(7, getValue("job-state")) // canceled
Expand Down

0 comments on commit 52267bd

Please sign in to comment.