Skip to content

Commit

Permalink
refactored client package
Browse files Browse the repository at this point in the history
  • Loading branch information
gmuth committed Nov 13, 2023
1 parent b654165 commit 92d75e2
Show file tree
Hide file tree
Showing 16 changed files with 224 additions and 84 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dependencies {
// gradlew clean -x test build publishToMavenLocal
defaultTasks("assemble")

val javaVersion = "1.8"
val javaVersion = "11"
tasks.apply {

// Kotlin
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ org.gradle.jvmargs=-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M
systemProp.sonar.host.url=https://sonarcloud.io
systemProp.sonar.projectKey=gmuth_ipp-client-kotlin
systemProp.sonar.organization=gmuth
#systemProp.sonar.gradle.skipCompile=true
5 changes: 3 additions & 2 deletions src/main/kotlin/de/gmuth/ipp/attributes/Marker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ class Marker(
fun levelPercent() = 100 * level / highLevel
fun levelIsLow() = level < lowLevel

override fun toString() = "%-10s %3d %% %5s %-12s %-8s %s"
.format(color, levelPercent(), if (levelIsLow()) "(low)" else "", type, colorCode, name)
override fun toString() = "%-10s %3d %% %5s %-12s %-8s %s".format(
color, levelPercent(), if (levelIsLow()) "(low)" else "", type, colorCode, name
)

enum class Color(val code: String) {
NONE("NONE"),
Expand Down
38 changes: 32 additions & 6 deletions src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ class CupsClient(
.apply { logger.finer { "cupsPrinterUri($printerName) = $this" } }

val version: String by lazy {
getPrinters().run {
if (isNotEmpty()) last()
else IppPrinter(getJobs(All).last().printerUri)
}.cupsVersion
try {
getPrinters().run {
if (isNotEmpty()) last()
else IppPrinter(getJobs(All).last().printerUri)
}.cupsVersion
} catch (exception: NoSuchElementException) {
"?"
}
}

fun getPrinters() =
Expand Down Expand Up @@ -166,7 +170,9 @@ class CupsClient(
ippClient.ippRequest(operation, printerURI)

private fun exchange(ippRequest: IppRequest) =
ippClient.exchange(ippRequest)
ippClient.exchange(ippRequest).also { this.httpServer = it.httpServer }

var httpServer: String? = null // from response after message exchange

//---------
// Get Jobs
Expand All @@ -182,6 +188,21 @@ class CupsClient(
fun getJob(id: Int) =
cupsServer.getJob(id)

//------------------
// Get Subscriptions
//------------------

fun getSubscriptions() =
cupsServer.getSubscriptions()

fun getSubscription(id: Int) =
cupsServer.getSubscription(id)

fun getOwnersOfAllSubscriptions() =
getSubscriptions()
.map { it.subscriberUserName }
.toSet()

//--------------------
// Create Subscription
//--------------------
Expand Down Expand Up @@ -234,7 +255,7 @@ class CupsClient(
enable()
resume()
updateAttributes()
if(savePPD) savePPD(cupsClientWorkDirectory)
if (savePPD) savePPD(cupsClientWorkDirectory)
}

// ---------------------------
Expand All @@ -243,6 +264,11 @@ class CupsClient(

private val jobOwners = mutableSetOf<String>()

fun getOwnersOfAllJobs() = getJobs(All)
.map { it.getOriginatingUserNameOrAppleJobOwnerOrNull() }
.filterNotNull()
.toSet()

fun getJobsAndSaveDocuments(
whichJobs: WhichJobs = All,
updateJobAttributes: Boolean = false,
Expand Down
12 changes: 9 additions & 3 deletions src/main/kotlin/de/gmuth/ipp/client/IppClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
errorStream
}
return decodeContentStream(request, responseCode, responseContentStream)
.apply { httpServer = headerFields["Server"]?.first() }
}
}

Expand Down Expand Up @@ -146,7 +147,11 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
contentStream: InputStream?,
cause: Exception? = null
) = when {
responseCode == 401 -> with(request) { "User \"$requestingUserName\" is not authorized for operation $operation on $printerOrJobUri" }
responseCode == 401 && request.operationGroup.containsKey("requesting-user-name") -> with(request) {
"User \"$requestingUserName\" is not authorized for operation $operation on $printerOrJobUri"
}

responseCode == 401 -> with(request) { "Not authorized for operation $operation on $printerOrJobUri (userName required)" }
responseCode == 426 -> "HTTP status $responseCode, $responseMessage, Try ipps://${request.printerOrJobUri.host}"
responseCode != 200 -> "HTTP request failed: $responseCode, $responseMessage"
contentType != null && !contentType.startsWith(APPLICATION_IPP) -> "Invalid Content-Type: $contentType"
Expand All @@ -169,8 +174,9 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
httpStatus: Int,
contentStream: InputStream
) = IppResponse().apply {
try { read(contentStream) }
catch (throwable: Throwable) {
try {
read(contentStream)
} catch (throwable: Throwable) {
throw IppExchangeException(
request, this, httpStatus, message = "Failed to decode ipp response", cause = throwable
).apply {
Expand Down
33 changes: 28 additions & 5 deletions src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,28 @@ import de.gmuth.ipp.attributes.JobState
import de.gmuth.ipp.attributes.PrinterState
import de.gmuth.ipp.core.IppAttributesGroup
import de.gmuth.ipp.core.IppString
import java.net.URI
import java.nio.charset.Charset
import java.time.ZonedDateTime
import java.util.logging.Level
import java.util.logging.Logger

class IppEventNotification(
val subscription: IppSubscription,
val attributes: IppAttributesGroup
) {
val charset: Charset
get() = attributes.getValue("notify-charset")

val sequenceNumber: Int
get() = attributes.getValue("notify-sequence-number")
val naturalLanguage: String
get() = attributes.getValue("notify-natural-language")

val subscriptionId: Int
get() = attributes.getValue("notify-subscription-id")

val sequenceNumber: Int
get() = attributes.getValue("notify-sequence-number")

val subscribedEvent: String
get() = attributes.getValue("notify-subscribed-event")

Expand All @@ -40,6 +48,9 @@ class IppEventNotification(
val jobImpressionsCompleted: Int
get() = attributes.getValue("job-impressions-completed")

val printerUri: URI
get() = attributes.getValue("notify-printer-uri")

val printerName: IppString
get() = attributes.getValue("printer-name")

Expand All @@ -52,12 +63,24 @@ class IppEventNotification(
val printerIsAcceptingJobs: Boolean
get() = attributes.getValue("printer-is-accepting-jobs")

// Get job from printer
// let a Recipient know when the Event Notification occurred (RFC 3996 5.2.2)
val printerUpTime: ZonedDateTime
get() = attributes.getZonedDateTimeValue("printer-up-time")

// Get job of event origin
fun getJob() = subscription.printer.getJob(jobId)

// Get printer of event origin
fun getPrinter(getPrinterAttributesOnInit: Boolean = false) = IppPrinter(
printerUri,
ippClient = subscription.printer.ippClient,
getPrinterAttributesOnInit = getPrinterAttributesOnInit
)

@SuppressWarnings("kotlin:S3776")
override fun toString() = StringBuilder().run {
append("EventNotification #$sequenceNumber:")
append(printerUpTime.toLocalDateTime())
append(" EventNotification #$sequenceNumber")
append(" [$subscribedEvent] $text")
with(attributes) {
if (containsKey("notify-job-id")) append(", job #$jobId")
Expand All @@ -72,6 +95,6 @@ class IppEventNotification(

@JvmOverloads
fun log(logger: Logger, level: Level = Level.INFO) =
attributes.log(logger, level, title = "EVENTNOTIFICATION #$sequenceNumber $subscribedEvent")
attributes.log(logger, level, title = "EVENT_NOTIFICATION #$sequenceNumber [$subscribedEvent] $text")

}
35 changes: 26 additions & 9 deletions src/main/kotlin/de/gmuth/ipp/client/IppJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class IppJob(
companion object {
var defaultDelay: Duration = Duration.ofSeconds(1)
var useJobOwnerAsUserName: Boolean = false
var useJobUri: Boolean = false
}

//--------------
Expand Down Expand Up @@ -77,21 +78,19 @@ class IppJob(
get() = attributes.getValue("job-k-octets")

val numberOfDocuments: Int
get() = attributes.getValueOrNull("number-of-documents")
?: attributes.getValueOrNull("document-count") // CUPS 1.x
?: throw IppException("number-of-documents or document-count not found")
get() = attributes.getValue("number-of-documents")

val documentNameSupplied: IppString
get() = attributes.getValue("document-name-supplied")

val timeAtCreation: ZonedDateTime
get() = attributes.getTimeValue("time-at-creation")
get() = attributes.getZonedDateTimeValue("time-at-creation")

val timeAtProcessing: ZonedDateTime
get() = attributes.getTimeValue("time-at-processing")
get() = attributes.getZonedDateTimeValue("time-at-processing")

val timeAtCompleted: ZonedDateTime
get() = attributes.getTimeValue("time-at-completed")
get() = attributes.getZonedDateTimeValue("time-at-completed")

val appleJobOwner: String // only supported by Apple CUPS
get() = attributes.getTextValue("com.apple.print.JobInfo.PMJobOwner")
Expand Down Expand Up @@ -129,6 +128,13 @@ class IppJob(
else -> null
}

val numberOfDocumentsOrDocumentCount: Int
get() = when {
attributes.containsKey("number-of-documents") -> numberOfDocuments
attributes.containsKey("document-count") -> attributes.getValue<Int>("document-count")
else -> throw IppException("number-of-documents or document-count not found")
}

//-------------------
// Get-Job-Attributes
//-------------------
Expand Down Expand Up @@ -315,7 +321,7 @@ class IppJob(
save: Boolean = false,
optionalCommandToHandleFile: String? = null
) =
(1..numberOfDocuments)
(1..numberOfDocumentsOrDocumentCount)
.map { cupsGetDocument(it) }
.onEach { document ->
if (save) with(document) {
Expand All @@ -339,7 +345,18 @@ class IppJob(
useJobOwnerAsUserName && attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> appleJobOwner
else -> printer.ippConfig.userName
}
).apply { operationGroup.attribute("job-uri", Uri, uri) }
).apply {
operationGroup.run {
if (useJobUri) {
// depending on network and CUPS config job uris might not be reachable
attribute("job-uri", Uri, uri)
} else {
// play save, this uri has worked before
attribute("printer-uri", Uri, printer.printerUri)
attribute("job-id", Integer, id)
}
}
}

// -------
// Logging
Expand All @@ -355,7 +372,7 @@ class IppJob(
if (containsKey("job-originating-host-name")) append(", originating-host-name=$originatingHostName")
if (containsKey("job-originating-user-name")) append(", originating-user-name=$originatingUserName")
if (containsKey("com.apple.print.JobInfo.PMJobOwner")) append(", appleJobOwner=$appleJobOwner")
if (containsKey("number-of-documents") || containsKey("document-count")) append(", number-of-documents=$numberOfDocuments")
if (containsKey("number-of-documents") || containsKey("document-count")) append(", $numberOfDocumentsOrDocumentCount documents")
if (containsKey("job-printer-uri")) append(", printer-uri=$printerUri")
if (containsKey("job-uri")) append(", uri=$uri")
toString()
Expand Down
35 changes: 20 additions & 15 deletions src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ class IppPrinter(
fun supportsOperations(vararg operations: IppOperation) = operationsSupported.containsAll(operations.toList())
fun supportsVersion(version: String) = versionsSupported.contains(version)
fun isCups() = attributes.contains("cups-version")
fun paused() = stateReasons.contains("paused")
fun offlineReport() = stateReasons.contains("offline-report")
fun mediaEmptyReport() = stateReasons.contains("media-empty-report")

//-----------------
// Identify-Printer
Expand Down Expand Up @@ -372,10 +375,9 @@ class IppPrinter(
// Get-Job-Attributes (as IppJob)
//-------------------------------

fun getJob(jobId: Int) =
exchangeForIppJob(
ippRequest(GetJobAttributes).apply { operationGroup.attribute("job-id", Integer, jobId) }
)
fun getJob(jobId: Int) = exchangeForIppJob(
ippRequest(GetJobAttributes).apply { operationGroup.attribute("job-id", Integer, jobId) }
)

//---------------------------------
// Get-Jobs (as Collection<IppJob>)
Expand Down Expand Up @@ -428,8 +430,8 @@ class IppPrinter(
checkNotifyEvents(notifyEvents)
createSubscriptionAttributesGroup(notifyEvents, notifyLeaseDuration, notifyTimeInterval)
}
val subscriptionAttributes = exchange(request).subscriptionGroup
return IppSubscription(this, subscriptionAttributes)
return IppSubscription(this, exchange(request).subscriptionGroup)
.apply { logger.info { "Created $this" } }
}

fun checkNotifyEvents(notifyEvents: Collection<String>?) = notifyEvents?.let {
Expand All @@ -443,11 +445,10 @@ class IppPrinter(

fun getSubscription(id: Int) = IppSubscription(
this,
exchange(
ippRequest(GetSubscriptionAttributes)
.apply { operationGroup.attribute("notify-subscription-id", Integer, id) }
)
.subscriptionGroup
exchange(ippRequest(GetSubscriptionAttributes).apply {
operationGroup.attribute("notify-subscription-id", Integer, id)
}).subscriptionGroup,
startLease = false
)

//---------------------------------------------
Expand All @@ -458,7 +459,7 @@ class IppPrinter(
notifyJobId: Int? = null,
mySubscriptions: Boolean? = null,
limit: Int? = null,
requestedAttributes: List<String>? = null
requestedAttributes: Collection<String>? = null
): List<IppSubscription> {
val request = ippRequest(GetSubscriptions, requestedAttributes = requestedAttributes).apply {
operationGroup.run {
Expand All @@ -467,9 +468,13 @@ class IppPrinter(
limit?.let { attribute("limit", Integer, it) }
}
}
return exchange(request)
.getAttributesGroups(Subscription)
.map { IppSubscription(this, it) }
return try {
exchange(request)
.getAttributesGroups(Subscription)
.map { IppSubscription(this, it, startLease = false) }
} catch (notFoundException: IppExchangeException.ClientErrorNotFoundException) {
emptyList()
}
}

//----------------------
Expand Down
Loading

0 comments on commit 92d75e2

Please sign in to comment.