Skip to content

Commit

Permalink
refactored package client, CUPS-Move-Jobs, IppJob uses job-uri
Browse files Browse the repository at this point in the history
  • Loading branch information
gmuth committed Oct 12, 2023
1 parent 24955e7 commit de8c491
Show file tree
Hide file tree
Showing 19 changed files with 385 additions and 304 deletions.
59 changes: 28 additions & 31 deletions src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package de.gmuth.ipp.client

import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException
import de.gmuth.ipp.client.WhichJobs.All
import de.gmuth.ipp.core.IppAttributesGroup
import de.gmuth.ipp.core.IppOperation
import de.gmuth.ipp.core.IppOperation.*
import de.gmuth.ipp.core.IppRequest
Expand All @@ -22,7 +21,7 @@ import java.util.logging.Logger.getLogger
// https://www.cups.org/doc/spec-ipp.html
class CupsClient(
val cupsUri: URI = URI.create("ipp://localhost"),
val ippClient: IppClient = IppClient()
private val ippClient: IppClient = IppClient()
) {
constructor(host: String = "localhost") : this(URI.create("ipp://$host"))

Expand All @@ -31,6 +30,10 @@ class CupsClient(
var userName: String? by config::userName
var cupsClientWorkDirectory = File("CUPS/${cupsUri.host}")

private val cupsServer =
IppPrinter(cupsUri, ippClient = ippClient, getPrinterAttributesOnInit = false)
.apply { workDirectory = cupsClientWorkDirectory }

init {
if (cupsUri.scheme == "ipps") config.trustAnyCertificateAndSSLHostname()
}
Expand Down Expand Up @@ -160,55 +163,53 @@ class CupsClient(
// Delegate to IppClient
//----------------------

fun basicAuth(user: String, password: String) =
ippClient.basicAuth(user, password)

internal fun ippRequest(operation: IppOperation, printerURI: URI = cupsUri) =
ippClient.ippRequest(operation, printerURI)

internal fun exchange(ippRequest: IppRequest) =
ippClient.exchange(ippRequest)

fun basicAuth(user: String, password: String) =
ippClient.basicAuth(user, password)

//-----------------------
// Delegate to IppPrinter
//-----------------------

protected val ippPrinter: IppPrinter by lazy {
IppPrinter(cupsUri, ippClient = ippClient, getPrinterAttributesOnInit = false)
.apply { workDirectory = cupsClientWorkDirectory }
}
//---------
// Get Jobs
//---------

fun getJobs(
whichJobs: WhichJobs? = null,
limit: Int? = null,
requestedAttributes: List<String>? = ippPrinter.getJobsRequestedAttributes
requestedAttributes: List<String>? = cupsServer.getJobsRequestedAttributes
) =
ippPrinter.getJobs(whichJobs = whichJobs, limit = limit, requestedAttributes = requestedAttributes)
cupsServer.getJobs(whichJobs = whichJobs, limit = limit, requestedAttributes = requestedAttributes)

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

//----------------------------
// Create printer subscription
//----------------------------
//--------------------
// Create Subscription
//--------------------

fun createPrinterSubscription(
fun createSubscription(
// https://datatracker.ietf.org/doc/html/rfc3995#section-5.3.3.4.2
notifyEvents: List<String>? = listOf("all"),
notifyLeaseDuration: Duration? = null,
notifyTimeInterval: Duration? = null
) =
ippPrinter.createPrinterSubscription(notifyEvents, notifyLeaseDuration, notifyTimeInterval)
cupsServer.createPrinterSubscription(notifyEvents, notifyLeaseDuration, notifyTimeInterval)

fun createPrinterSubscription(
fun createSubscription(
vararg notifyEvents: String = arrayOf("all"),
notifyLeaseDuration: Duration? = null,
notifyTimeInterval: Duration? = null
) =
createPrinterSubscription(notifyEvents.toList(), notifyLeaseDuration, notifyTimeInterval)
createSubscription(notifyEvents.toList(), notifyLeaseDuration, notifyTimeInterval)

//-----------------------------
// Setup IPP Everywhere Printer
// Create IPP Everywhere Printer
//-----------------------------

fun setupIppEverywherePrinter(
fun createIppEverywherePrinter(
printerName: String,
deviceUri: URI,
printerInfo: String? = null,
Expand All @@ -218,7 +219,7 @@ class CupsClient(
deviceUri,
printerInfo,
printerLocation,
ppdName = "everywhere"
ppdName = "airprint"
).apply {
updateAttributes("printer-name")
log.info(toString())
Expand All @@ -238,9 +239,6 @@ class CupsClient(
updateAttributes()
}

private val IppRequest.printerGroup: IppAttributesGroup
get() = getSingleAttributesGroup(Printer)

// ---------------------------
// Get jobs and save documents
// ---------------------------
Expand Down Expand Up @@ -278,7 +276,7 @@ class CupsClient(
.apply {
log.info { "Found ${jobOwners.size} job ${if (jobOwners.size == 1) "owner" else "owners"}: $jobOwners" }
log.info { "Found $size jobs (which=$whichJobs) where $numberOfJobsWithoutDocuments jobs have no documents" }
log.info { "Saved $numberOfSavedDocuments documents of ${size.minus(numberOfJobsWithoutDocuments.toInt())} jobs with documents to directory: ${ippPrinter.workDirectory}" }
log.info { "Saved $numberOfSavedDocuments documents of ${size.minus(numberOfJobsWithoutDocuments.toInt())} jobs with documents to directory: ${cupsServer.workDirectory}" }
}
}

Expand All @@ -294,7 +292,7 @@ class CupsClient(
pollEvery: Duration = Duration.ofSeconds(1),
commandToHandleFile: String? = null // e.g. "open" -> open <filename> with Preview on MacOS
) {
createPrinterSubscription(whichJobEvents, notifyLeaseDuration = leaseDuration)
createSubscription(whichJobEvents, notifyLeaseDuration = leaseDuration)
.pollAndHandleNotifications(pollEvery, autoRenewSubscription = autoRenewLease) { event ->
log.info { event.toString() }
with(event.getJob()) {
Expand Down Expand Up @@ -343,5 +341,4 @@ class CupsClient(
}
return documents.map { it.file!! }
}

}
16 changes: 7 additions & 9 deletions src/main/kotlin/de/gmuth/ipp/client/IppClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import javax.net.ssl.HttpsURLConnection

typealias IppResponseInterceptor = (request: IppRequest, response: IppResponse) -> Unit

open class IppClient(val config: IppConfig = IppConfig()) {
open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
protected val log = getLogger(javaClass.name)

var responseInterceptor: IppResponseInterceptor? = null
Expand All @@ -47,18 +47,16 @@ open class IppClient(val config: IppConfig = IppConfig()) {
// Build IppRequest
//-----------------

protected val requestCounter = AtomicInteger(1)
private val requestCounter = AtomicInteger(1)

fun ippRequest(
operation: IppOperation,
printerUri: URI,
jobId: Int? = null,
printerUri: URI? = null,
requestedAttributes: Collection<String>? = null,
userName: String? = config.userName
) = IppRequest(
operation,
printerUri,
jobId,
requestedAttributes,
userName,
config.ippVersion,
Expand All @@ -71,8 +69,8 @@ open class IppClient(val config: IppConfig = IppConfig()) {
// Exchange IppRequest for IppResponse
//------------------------------------

fun exchange(request: IppRequest): IppResponse {
val ippUri: URI = request.printerUri
override fun exchange(request: IppRequest): IppResponse {
val ippUri: URI = request.printerOrJobUri
log.finer { "send '${request.operation}' request to $ippUri" }
val response = httpPost(toHttpUri(ippUri), request)
log.fine { "$ippUri: $request => $response" }
Expand Down Expand Up @@ -151,11 +149,11 @@ open class IppClient(val config: IppConfig = IppConfig()) {
) =
when {
responseCode == 401 -> with(request) {
"User \"$requestingUserName\" is not authorized for operation $operation on $printerUri"
"User \"$requestingUserName\" is not authorized for operation $operation on $printerOrJobUri"
}

responseCode == 426 -> {
"HTTP status $responseCode, $responseMessage, Try ipps://${request.printerUri.host}"
"HTTP status $responseCode, $responseMessage, Try ipps://${request.printerOrJobUri.host}"
}

responseCode != 200 -> {
Expand Down
33 changes: 17 additions & 16 deletions src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import de.gmuth.ipp.core.IppString
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.logging.Logger
import java.io.OutputStream
import java.util.logging.Logger.getLogger
import kotlin.io.path.createTempDirectory

class IppDocument(
val job: IppJob,
val attributes: IppAttributesGroup,
val inputStream: InputStream
) {
private val job: IppJob,
documentAttributes: IppAttributesGroup,
private val inputStream: InputStream
) : IppObject(job, documentAttributes) {

private val log = getLogger(javaClass.name)

val number: Int
Expand All @@ -31,9 +32,8 @@ class IppDocument(

var file: File? = null

fun readBytes() = inputStream.readBytes().also {
log.fine { "read ${it.size} bytes of $this" }
}
fun readBytes() = inputStream.readBytes()
.also { log.fine { "Read ${it.size} bytes of $this" } }

fun filenameSuffix() = when (format) {
"application/octet-stream" -> "bin"
Expand All @@ -46,7 +46,7 @@ class IppDocument(

fun filename() = StringBuilder().run {
var suffix: String? = filenameSuffix()
with(job) {
job.run {
append("job-$id")
if (numberOfDocuments > 1) append("-doc-$number")
job.getOriginatingUserNameOrAppleJobOwnerOrNull()?.let { append("-$it") }
Expand All @@ -62,28 +62,29 @@ class IppDocument(
toString().replace('/', '_')
}

fun copyTo(outputStream: OutputStream) =
inputStream.copyTo(outputStream)

fun save(
directory: File = createTempDirectory().toFile(),
file: File = File(directory, filename()),
overwrite: Boolean = true
) = file.also {
if (file.isFile && !overwrite) throw IOException("File '$file' already exists")
inputStream.copyTo(file.outputStream())
copyTo(file.outputStream())
this.file = file
log.info { "Saved $this" }
}

fun runCommand(commandToHandleFile: String) =
Runtime.getRuntime().exec(arrayOf(commandToHandleFile, file!!.absolutePath))

override fun toString() = StringBuilder().run {
append("document #$number ($format) of job #${job.id}")
override fun objectName() = "Document #$number"

override fun toString() = StringBuilder(objectName()).run {
append(" ($format) of job #${job.id}")
if (attributes.containsKey("document-name")) append(" '$name'")
if (file != null) append(": $file (${file!!.length()} bytes)")
toString()
}

fun log(logger: Logger) =
attributes.log(logger, title = "DOCUMENT-$number")

}
18 changes: 6 additions & 12 deletions src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ 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.util.logging.Logger

class IppEventNotification(
val subscription: IppSubscription,
val attributes: IppAttributesGroup
) {
private val subscription: IppSubscription,
eventNotificationAttributes: IppAttributesGroup
) : IppObject(subscription, eventNotificationAttributes) {

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

Expand Down Expand Up @@ -50,12 +50,10 @@ class IppEventNotification(
val printerIsAcceptingJobs: Boolean
get() = attributes.getValue("printer-is-accepting-jobs")

// get job from printer
// Get job from printer
fun getJob() = subscription.printer.getJob(jobId)

// -------
// Logging
// -------
override fun objectName() = "Event notification #$sequenceNumber $subscribedEvent"

@SuppressWarnings("kotlin:S3776")
override fun toString() = StringBuilder().run {
Expand All @@ -71,8 +69,4 @@ class IppEventNotification(
}
toString()
}

fun log(logger: Logger) =
attributes.log(logger, title = "event notification #$sequenceNumber $subscribedEvent")

}
14 changes: 14 additions & 0 deletions src/main/kotlin/de/gmuth/ipp/client/IppExchange.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.gmuth.ipp.client

import de.gmuth.ipp.core.IppRequest
import de.gmuth.ipp.core.IppResponse

/**
* Copyright (c) 2020-2023 Gerhard Muth
*/

fun interface IppExchange {

fun exchange(request: IppRequest): IppResponse

}
Loading

0 comments on commit de8c491

Please sign in to comment.