diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index 68f0245c..d8f39710 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -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 @@ -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")) @@ -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() } @@ -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? = ippPrinter.getJobsRequestedAttributes + requestedAttributes: List? = 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? = 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, @@ -218,7 +219,7 @@ class CupsClient( deviceUri, printerInfo, printerLocation, - ppdName = "everywhere" + ppdName = "airprint" ).apply { updateAttributes("printer-name") log.info(toString()) @@ -238,9 +239,6 @@ class CupsClient( updateAttributes() } - private val IppRequest.printerGroup: IppAttributesGroup - get() = getSingleAttributesGroup(Printer) - // --------------------------- // Get jobs and save documents // --------------------------- @@ -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}" } } } @@ -294,7 +292,7 @@ class CupsClient( pollEvery: Duration = Duration.ofSeconds(1), commandToHandleFile: String? = null // e.g. "open" -> open with Preview on MacOS ) { - createPrinterSubscription(whichJobEvents, notifyLeaseDuration = leaseDuration) + createSubscription(whichJobEvents, notifyLeaseDuration = leaseDuration) .pollAndHandleNotifications(pollEvery, autoRenewSubscription = autoRenewLease) { event -> log.info { event.toString() } with(event.getJob()) { @@ -343,5 +341,4 @@ class CupsClient( } return documents.map { it.file!! } } - } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index 348d45c6..749fbbfb 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -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 @@ -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? = null, userName: String? = config.userName ) = IppRequest( operation, printerUri, - jobId, requestedAttributes, userName, config.ippVersion, @@ -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" } @@ -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 -> { diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index 9edf4eff..8aec0e98 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -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 @@ -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" @@ -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") } @@ -62,13 +62,16 @@ 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" } } @@ -76,14 +79,12 @@ class IppDocument( 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") - } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt index e4724175..b30af589 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt @@ -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") @@ -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 { @@ -71,8 +69,4 @@ class IppEventNotification( } toString() } - - fun log(logger: Logger) = - attributes.log(logger, title = "event notification #$sequenceNumber $subscribedEvent") - } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchange.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchange.kt new file mode 100644 index 00000000..8af08fcb --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchange.kt @@ -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 + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt b/src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt new file mode 100644 index 00000000..28319a95 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt @@ -0,0 +1,138 @@ +package de.gmuth.ipp.client + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import de.gmuth.ipp.attributes.* +import de.gmuth.ipp.attributes.TemplateAttributes.jobName +import de.gmuth.ipp.core.IppOperation.* +import java.io.File +import java.net.URI +import java.util.logging.Logger.getLogger + +object IppInspector { + private val log = getLogger(javaClass.name) + var directory: String = "inspected-printers" + + const val pdfA4 = "blank_A4.pdf" + + fun inspect(printerUri: URI, cancelJob: Boolean = true) = + IppPrinter(printerUri).inspect(cancelJob) + + /** + * Exchange a few IPP requests and save the IPP responses returned by the printer. + * Operations: + * - Get-Printer-Attributes + * - Print-Job, Get-Jobs, Get-Job-Attributes + * - Hold-Job, Release-Job, Cancel-Job + */ + private fun IppPrinter.inspect(cancelJob: Boolean) { + log.info { "Inspect printer $printerUri" } + + val printerModel = with(StringBuilder()) { + if (isCups()) append("CUPS-") + append(makeAndModel.text.replace("\\s+".toRegex(), "_")) + toString() + } + log.info { "Printer model: $printerModel" } + + ippClient.saveMessages = true + ippClient.saveMessagesDirectory = File(directory, printerModel).apply { + if (!isDirectory && !mkdirs()) throw RuntimeException("Failed to create directory: $path") + } + workDirectory = ippClient.saveMessagesDirectory + + attributes.run { + // Media + if (containsKey("media-supported")) log.info { "Media supported: $mediaSupported" } + if (containsKey("media-ready")) log.info { "Media ready: $mediaReady" } + if (containsKey("media-default")) log.info { "Media default: $mediaDefault" } + // URIs + log.info { "Communication channels supported:" } + communicationChannelsSupported.forEach { log.info { " $it" } } + } + + val pdfResource = when { + !attributes.containsKey("media-ready") -> { + log.warning { "media-ready not supported" } + pdfA4 + } + + mediaReady.contains("iso-a4") || mediaReady.contains("iso_a4_210x297mm") -> pdfA4 + mediaReady.contains("na_letter") || mediaReady.contains("na_letter_8.5x11in") -> "blank_USLetter.pdf" + else -> { + log.warning { "No PDF available for media '$mediaReady', trying A4" } + pdfA4 + } + } + + ippConfig.userName = "ipp-inspector" + runInspectionWorkflow(pdfResource, cancelJob) + } + + private fun IppPrinter.runInspectionWorkflow(pdfResource: String, cancelJob: Boolean) { + + log.info { "> Get printer attributes" } + getPrinterAttributes() + + if (supportsOperations(CupsGetPPD)) { + log.info { "> CUPS Get PPD" } + savePPD(file = File(workDirectory, "0-${name.text}.ppd")) + } + + if (supportsOperations(IdentifyPrinter)) { + val action = with(identifyActionsSupported) { if (contains("sound")) "sound" else first() } + log.info { "> Identify by $action" } + identify(action) + } + + log.info { "> Validate job" } + val response = try { + validateJob( + jobName("Validation"), + DocumentFormat.OCTET_STREAM, + Sides.TwoSidedShortEdge, + PrintQuality.Normal, + ColorMode.Color, + Media.ISO_A3 + ) + } catch (ippExchangeException: IppExchangeException) { + ippExchangeException.response + } + log.info { response.toString() } + + log.info { "> Print job $pdfResource" } + printJob( + IppInspector::class.java.getResourceAsStream("/$pdfResource"), + jobName(pdfResource), + + ).run { + log.info { toString() } + + log.info { "> Get jobs" } + for (job in getJobs()) { + log.info { "$job" } + } + + if (supportsOperations(HoldJob, ReleaseJob)) { + log.info { "> Hold job" } + hold() + log.info { "> Release job" } + release() + } + + if (cancelJob) { + log.info { "> Cancel job" } + cancel() + } + + log.info { "> Update job attributes" } + updateAttributes() + + ippClient.saveMessages = false + log.info { "> Wait for termination" } + waitForTermination() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index cdec6523..5dda5b6d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -15,16 +15,14 @@ import java.io.InputStream import java.net.URI import java.time.Duration import java.time.ZonedDateTime -import java.util.logging.Level -import java.util.logging.Level.INFO -import java.util.logging.Logger import java.util.logging.Logger.getLogger +@SuppressWarnings("kotlin:S1192") class IppJob( val printer: IppPrinter, - var attributes: IppAttributesGroup, + jobAttributes: IppAttributesGroup, subscriptionAttributes: IppAttributesGroup? = null -) { +) : IppObject(printer, jobAttributes) { companion object { var defaultDelay: Duration = Duration.ofSeconds(1) @@ -33,7 +31,6 @@ class IppJob( private val log = getLogger(javaClass.name) var subscription: IppSubscription? = subscriptionAttributes?.let { IppSubscription(printer, it) } - val ippConfig = printer.ippConfig //-------------- // IppAttributes @@ -52,15 +49,12 @@ class IppJob( get() = JobState.fromAttributes(attributes) val stateReasons: List - @SuppressWarnings("kotlin:S1192") get() = attributes.getValues("job-state-reasons") val name: IppString - @SuppressWarnings("kotlin:S1192") get() = attributes.getValue("job-name") val originatingUserName: IppString - @SuppressWarnings("kotlin:S1192") get() = attributes.getValue("job-originating-user-name") val originatingHostName: IppString @@ -93,7 +87,6 @@ class IppJob( get() = attributes.getTimeValue("time-at-completed") val appleJobOwner: String // only supported by Apple CUPS - @SuppressWarnings("kotlin:S1192") get() = attributes.getTextValue("com.apple.print.JobInfo.PMJobOwner") fun isPending(updateStateAttributes: Boolean = false) = stateIs(updateStateAttributes, Pending) @@ -105,7 +98,7 @@ class IppJob( fun isTerminated(updateStateAttributes: Boolean = false) = stateIs(updateStateAttributes, Canceled) || stateIs(false, Aborted) || stateIs(false, Completed) - protected fun stateIs(updateStateAttributes: Boolean, expectedState: JobState): Boolean { + private fun stateIs(updateStateAttributes: Boolean, expectedState: JobState): Boolean { if (updateStateAttributes) updateAttributes("job-state", "job-state-reasons") return state == expectedState } @@ -210,7 +203,7 @@ class IppJob( val request = documentRequest(SendDocument, lastDocument, documentName, documentNaturalLanguage).apply { documentInputStream = inputStream } - attributes = exchange(request).jobGroup + attributes.put(exchange(request).jobGroup) } @JvmOverloads @@ -235,10 +228,10 @@ class IppJob( val request = documentRequest(SendURI, lastDocument, documentName, documentNaturalLanguage).apply { operationGroup.attribute("document-uri", Uri, documentUri) } - attributes = exchange(request).jobGroup + attributes.put(exchange(request).jobGroup) } - protected fun documentRequest( + private fun documentRequest( operation: IppOperation, lastDocument: Boolean, documentName: String?, @@ -251,20 +244,35 @@ class IppJob( } } + //-------------- + // CUPS-Move-Job + //-------------- + + fun cupsMoveJob(printerUri: URI) = exchange( + ippRequest(CupsMoveJob).apply { + require(uri.host == printerUri.host) { "Printer $printerUri must be on the same server: ${uri.host}" } + createAttributesGroup(Job).attribute("job-printer-uri", Uri, printerUri) + } + ) + + fun cupsMoveJob(ippPrinter: IppPrinter) = + cupsMoveJob(ippPrinter.printerUri) + //------------------------ // Create-Job-Subscription //------------------------ + @JvmOverloads fun createJobSubscription(notifyEvents: List? = null): IppSubscription { val request = ippRequest(CreateJobSubscriptions).apply { printer.checkNotifyEvents(notifyEvents) createSubscriptionAttributesGroup(notifyEvents, notifyJobId = id) } - val subscriptionAttributes = exchange(request).getSingleAttributesGroup(Subscription) + val subscriptionAttributes = exchange(request).subscriptionGroup return IppSubscription(printer, subscriptionAttributes).apply { subscription = this if (notifyEvents != null && !events.containsAll(notifyEvents)) { - log.warning { "server ignored some notifyEvents $notifyEvents, subscribed events: $events" } + log.warning { "Server ignored some notifyEvents $notifyEvents, subscribed events: $events" } } } } @@ -291,7 +299,7 @@ class IppJob( @JvmOverloads fun cupsGetDocument(documentNumber: Int = 1): IppDocument { - log.fine { "cupsGetDocument #$documentNumber for job #$id" } + log.fine { "CupsGetDocument #$documentNumber for job #$id" } val response = exchange(ippRequest(CupsGetDocument).apply { operationGroup.attribute("document-number", Integer, documentNumber) }) @@ -319,28 +327,26 @@ class IppJob( fun printerDirectory() = printer.printerDirectory(printerUri.toString().substringAfterLast("/")) - fun ippRequest(operation: IppOperation, requestedAttributes: List? = null) = + private fun ippRequest(operation: IppOperation, requestedAttributes: List? = null) = printer.ippRequest( - operation, id, requestedAttributes, + operation, requestedAttributes, printerUri = null, userName = when { useJobOwnerAsUserName && attributes.containsKey("job-originating-user-name") -> originatingUserName.text useJobOwnerAsUserName && attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> appleJobOwner - else -> ippConfig.userName + else -> printer.ippConfig.userName } - ) - - fun exchange(request: IppRequest) = - printer.exchange(request) + ).apply { operationGroup.attribute("job-uri", Uri, uri) } // ------- // Logging // ------- + override fun objectName() = "Job #$id" + @SuppressWarnings("kotlin:S3776") - override fun toString(): String = with(attributes) { - StringBuffer().run { - append("Job #$id:") - if (containsKey("job-state")) append(" state=$state") + override fun toString(): String = attributes.run { + StringBuilder(objectName()).run { + if (containsKey("job-state")) append(", state=$state") if (containsKey("job-state-reasons")) append(" (reasons=${stateReasons.joinToString(",")})") if (containsKey("job-name")) append(", name=$name") if (containsKey("job-impressions-completed")) append(", impressions-completed=$impressionsCompleted") @@ -349,11 +355,8 @@ class IppJob( 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("job-printer-uri")) append(", printer-uri=$printerUri") + if (containsKey("job-uri")) append(", uri=$uri") toString() } } - - @JvmOverloads - fun log(logger: Logger, level: Level = INFO) = - attributes.log(logger, level, title = "JOB-$id") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppObject.kt b/src/main/kotlin/de/gmuth/ipp/client/IppObject.kt new file mode 100644 index 00000000..63d02cfe --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/client/IppObject.kt @@ -0,0 +1,26 @@ +package de.gmuth.ipp.client + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppRequest +import java.util.logging.Level +import java.util.logging.Logger + +abstract class IppObject( + private val exchangeDelegate: IppExchange, + internal val attributes: IppAttributesGroup +) : IppExchange { + + override fun exchange(request: IppRequest) = + exchangeDelegate.exchange(request) + + protected abstract fun objectName(): String + + @JvmOverloads + fun log(logger: Logger, level: Level = Level.INFO) = + attributes.log(logger, level, title = objectName()) + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 66519107..5679554f 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -8,7 +8,6 @@ import de.gmuth.ipp.attributes.* import de.gmuth.ipp.attributes.CommunicationChannel.Companion.getCommunicationChannelsSupported import de.gmuth.ipp.attributes.Marker.Companion.getMarkers import de.gmuth.ipp.attributes.PrinterState.* -import de.gmuth.ipp.attributes.TemplateAttributes.jobName import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound @@ -18,21 +17,20 @@ import de.gmuth.ipp.iana.IppRegistrationsSection2 import java.io.* import java.net.URI import java.time.Duration -import java.util.logging.Level import java.util.logging.Level.* -import java.util.logging.Logger import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory @SuppressWarnings("kotlin:S1192") -open class IppPrinter( +class IppPrinter( val printerUri: URI, - val attributes: IppAttributesGroup = IppAttributesGroup(Printer), + printerAttributes: IppAttributesGroup = IppAttributesGroup(Printer), ippConfig: IppConfig = IppConfig(), - val ippClient: IppClient = IppClient(ippConfig), + internal val ippClient: IppClient = IppClient(ippConfig), getPrinterAttributesOnInit: Boolean = true, requestedAttributesOnInit: List? = null -) { +) : IppObject(ippClient, printerAttributes) { + private val log = getLogger(javaClass.name) var workDirectory: File = createTempDirectory().toFile() @@ -69,7 +67,7 @@ open class IppPrinter( try { updateAttributes(requestedAttributesOnInit) if (isStopped()) { - log.info { toString() } + log.fine { toString() } alert?.let { log.info { "alert: $it" } } alertDescription?.let { log.info { "alert-description: $it" } } } @@ -102,6 +100,9 @@ open class IppPrinter( val ippConfig: IppConfig get() = ippClient.config + fun basicAuth(user: String, password: String) = + ippClient.basicAuth(user, password) + var getJobsRequestedAttributes = mutableListOf( "job-id", "job-uri", "job-printer-uri", "job-state", "job-name", "job-state-reasons", "job-originating-user-name" @@ -237,15 +238,28 @@ open class IppPrinter( // Printer administration //----------------------- - fun pause() = exchangeIppRequest(PausePrinter) - fun resume() = exchangeIppRequest(ResumePrinter) - fun purgeJobs() = exchangeIppRequest(PurgeJobs) - fun enable() = exchangeIppRequest(EnablePrinter) - fun disable() = exchangeIppRequest(DisablePrinter) - fun holdNewJobs() = exchangeIppRequest(HoldNewJobs) - fun releaseHeldNewJobs() = exchangeIppRequest(ReleaseHeldNewJobs) - fun cancelJobs() = exchangeIppRequest(CancelJobs) - fun cancelMyJobs() = exchangeIppRequest(CancelMyJobs) + fun pause() = exchange(ippRequest(PausePrinter)) + fun resume() = exchange(ippRequest(ResumePrinter)) + fun purgeJobs() = exchange(ippRequest(PurgeJobs)) + fun enable() = exchange(ippRequest(EnablePrinter)) + fun disable() = exchange(ippRequest(DisablePrinter)) + fun holdNewJobs() = exchange(ippRequest(HoldNewJobs)) + fun releaseHeldNewJobs() = exchange(ippRequest(ReleaseHeldNewJobs)) + fun cancelJobs() = exchange(ippRequest(CancelJobs)) + fun cancelMyJobs() = exchange(ippRequest(CancelMyJobs)) + + fun cupsGetPPD(copyTo: OutputStream? = null) = exchange(ippRequest(CupsGetPPD)) + .apply { copyTo?.let { documentInputStream!!.copyTo(it) } } + + fun savePPD( + directory: File = workDirectory, + filename: String = "$name.ppd", + file: File = File(directory, filename) + ) = + file.also { + cupsGetPPD(it.outputStream()) + log.info { "Saved PPD $it" } + } //------------------------------------------ // Get-Printer-Attributes @@ -358,7 +372,9 @@ open class IppPrinter( //------------------------------- fun getJob(jobId: Int) = - exchangeForIppJob(ippRequest(GetJobAttributes, jobId)) + exchangeForIppJob( + ippRequest(GetJobAttributes).apply { operationGroup.attribute("job-id", Integer, jobId) } + ) //--------------------------------- // Get-Jobs (as Collection) @@ -404,7 +420,7 @@ open class IppPrinter( checkNotifyEvents(notifyEvents) createSubscriptionAttributesGroup(notifyEvents, notifyLeaseDuration, notifyTimeInterval) } - val subscriptionAttributes = exchange(request).getSingleAttributesGroup(Subscription) + val subscriptionAttributes = exchange(request).subscriptionGroup return IppSubscription(this, subscriptionAttributes) } @@ -419,9 +435,11 @@ open class IppPrinter( fun getSubscription(id: Int) = IppSubscription( this, - exchange(ippRequest(GetSubscriptionAttributes).apply { - operationGroup.attribute("notify-subscription-id", Integer, id) - }).getSingleAttributesGroup(Subscription) + exchange( + ippRequest(GetSubscriptionAttributes) + .apply { operationGroup.attribute("notify-subscription-id", Integer, id) } + ) + .subscriptionGroup ) //--------------------------------------------- @@ -450,32 +468,30 @@ open class IppPrinter( // delegate to IppClient //---------------------- - fun ippRequest( + internal fun ippRequest( operation: IppOperation, - jobId: Int? = null, requestedAttributes: Collection? = null, - userName: String? = ippConfig.userName + userName: String? = ippConfig.userName, + printerUri: URI? = this.printerUri ) = ippClient - .ippRequest(operation, printerUri, jobId, requestedAttributes, userName) + .ippRequest(operation, printerUri, requestedAttributes, userName) - fun exchange(request: IppRequest) = ippClient.exchange(request.apply { + override fun exchange(request: IppRequest) = super.exchange(request.apply { checkIfValueIsSupported("ipp-versions-supported", version!!) checkIfValueIsSupported("operations-supported", code!!.toInt()) checkIfValueIsSupported("charset-supported", attributesCharset) }) - protected fun exchangeIppRequest(operation: IppOperation) = exchange(ippRequest(operation)) - - protected fun exchangeForIppJob(request: IppRequest): IppJob { + private fun exchangeForIppJob(request: IppRequest): IppJob { val response = exchange(request) if (response.status != SuccessfulOk) log.warning { "Job response status: ${response.status}" } if (request.containsGroup(Subscription) && !response.containsGroup(Subscription)) { request.log(log, WARNING, prefix = "REQUEST: ") - val events: List = request.getSingleAttributesGroup(Subscription).getValues("notify-events") + val events: List = request.subscriptionGroup.getValues("notify-events") throw IppException("printer/server did not create subscription for events: ${events.joinToString(",")}") } val subscriptionsAttributes = response.run { - if (containsGroup(Subscription)) getSingleAttributesGroup(Subscription) else null + if (containsGroup(Subscription)) subscriptionGroup else null } return IppJob(this, response.jobGroup, subscriptionsAttributes) } @@ -484,9 +500,11 @@ open class IppPrinter( // Logging // ------- - override fun toString() = StringBuilder("IppPrinter:").run { - if (attributes.containsKey("printer-name")) append(" name=$name") - if (attributes.containsKey("printer-make-and-model")) append(", makeAndModel=$makeAndModel") + override fun objectName() = "Printer $name ($makeAndModel)" + + override fun toString() = StringBuilder("Printer").run { + if (attributes.containsKey("printer-name")) append(" $name") + if (attributes.containsKey("printer-make-and-model")) append(" ($makeAndModel)") append(", state=$state, stateReasons=$stateReasons") stateMessage?.let { if (it.text.isNotEmpty()) append(", stateMessage=$stateMessage") } if (attributes.containsKey("printer-is-accepting-jobs")) append(", isAcceptingJobs=$isAcceptingJobs") @@ -495,10 +513,6 @@ open class IppPrinter( toString() } - @JvmOverloads - fun log(logger: Logger, level: Level = INFO) = - attributes.log(logger, level, title = "PRINTER-$name ($makeAndModel), $state $stateReasons") - // ------------------------------------------------------ // attribute value checking based on printer capabilities // ------------------------------------------------------ @@ -575,120 +589,4 @@ open class IppPrinter( if (!mkdirs() && !isDirectory) throw IOException("Failed to create printer directory: $path") } - /** - * Exchange a few IPP requests and save the IPP responses returned by the printer. - * Operations: - * - Get-Printer-Attributes - * - Print-Job, Get-Jobs, Get-Job-Attributes - * - Hold-Job, Release-Job, Cancel-Job - */ - @JvmOverloads - fun inspect(directory: String = "inspected-printers", cancelJob: Boolean = true) { - - log.info { "Inspect printer $printerUri" } - - val printerModel = with(StringBuilder()) { - if (isCups()) append("CUPS_") - append(makeAndModel.text.replace("\\s+".toRegex(), "_")) - toString() - } - log.info { "Printer model: $printerModel" } - - ippClient.saveMessages = true - ippClient.saveMessagesDirectory = File(directory, printerModel).apply { - if (!isDirectory && !mkdirs()) throw RuntimeException("Failed to create directory: $path") - } - - attributes.run { - // Media - if (containsKey("media-supported")) log.info { "Media supported: $mediaSupported" } - if (containsKey("media-ready")) log.info { "Media ready: $mediaReady" } - if (containsKey("media-default")) log.info { "Media default: $mediaDefault" } - // URIs - log.info { "Communication channels supported:" } - communicationChannelsSupported.forEach { log.info { " $it" } } - } - - val pdfResource = when { - !attributes.containsKey("media-ready") -> { - log.warning { "media-ready not supported" } - "blank_A4.pdf" - } - - mediaReady.contains("iso-a4") || mediaReady.contains("iso_a4_210x297mm") -> "blank_A4.pdf" - mediaReady.contains("na_letter") || mediaReady.contains("na_letter_8.5x11in") -> "blank_USLetter.pdf" - else -> { - log.warning { "No PDF available for media '$mediaReady', trying A4" } - "blank_A4.pdf" - } - } - - ippConfig.userName = "ipp-inspector" - runInspectionWorkflow(pdfResource, cancelJob) - } - - protected fun runInspectionWorkflow(pdfResource: String, cancelJob: Boolean) { - - log.info { "> Get printer attributes" } - getPrinterAttributes() - - if (supportsOperations(IdentifyPrinter)) { - val action = with(identifyActionsSupported) { if (contains("sound")) "sound" else first() } - log.info { "> Identify by $action" } - identify(action) - } - - log.info { "> Validate job" } - val response = try { - validateJob( - jobName("Validation"), - DocumentFormat.OCTET_STREAM, - Sides.TwoSidedShortEdge, - PrintQuality.Normal, - ColorMode.Color, - Media.ISO_A3 - ) - } catch (ippExchangeException: IppExchangeException) { - ippExchangeException.response - } - log.info { response.toString() } - - log.info { "> Print job $pdfResource" } - printJob( - IppPrinter::class.java.getResourceAsStream("/$pdfResource"), - jobName(pdfResource), - - ).run { - log.info { toString() } - - log.info { "> Get jobs" } - for (job in getJobs()) { - log.info { "$job" } - } - - if (supportsOperations(HoldJob, ReleaseJob)) { - log.info { "> Hold job" } - hold() - log.info { "> Release job" } - release() - } - - if (cancelJob) { - log.info { "> Cancel job" } - cancel() - } - - log.info { "> Update job attributes" } - updateAttributes() - - ippClient.saveMessages = false - if (!isTerminated()) { - log.info { "> Wait for termination" } - waitForTermination() - } - - if (isAborted()) log(log) - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt index 7af5c749..06d7c404 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt @@ -8,22 +8,20 @@ import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppOperation import de.gmuth.ipp.core.IppOperation.* -import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppString -import de.gmuth.ipp.core.IppTag.* +import de.gmuth.ipp.core.IppTag.EventNotification +import de.gmuth.ipp.core.IppTag.Integer import java.time.Duration import java.time.Duration.ofSeconds import java.time.LocalDateTime import java.time.LocalDateTime.now -import java.util.logging.Level -import java.util.logging.Level.INFO -import java.util.logging.Logger import java.util.logging.Logger.getLogger class IppSubscription( val printer: IppPrinter, - var attributes: IppAttributesGroup -) { + subscriptionAttributes: IppAttributesGroup +) : IppObject(printer, subscriptionAttributes) { + private val log = getLogger(javaClass.name) private var lastSequenceNumber: Int = 0 @@ -54,8 +52,6 @@ class IppSubscription( val timeInterval: Duration get() = ofSeconds(attributes.getValue("notify-time-interval").toLong()) - fun hasJobId() = attributes.containsKey("notify-job-id") - //---------------------------- // Get-Subscription-Attributes //---------------------------- @@ -64,10 +60,10 @@ class IppSubscription( @JvmOverloads fun getSubscriptionAttributes(requestedAttributes: List? = null) = - exchange(ippRequest(GetSubscriptionAttributes, requestedAttributes = requestedAttributes)) + exchange(ippRequest(GetSubscriptionAttributes, requestedAttributes = requestedAttributes)).subscriptionGroup fun updateAttributes() { - attributes.put(getSubscriptionAttributes().getSingleAttributesGroup(Subscription)) + attributes.put(getSubscriptionAttributes()) } //------------------ @@ -91,7 +87,8 @@ class IppSubscription( // Cancel-Subscription //-------------------- - fun cancel() = exchange(ippRequest(CancelSubscription)) + fun cancel() = + exchange(ippRequest(CancelSubscription)) //------------------- // Renew-Subscription @@ -107,18 +104,15 @@ class IppSubscription( } //----------------------- - // delegate to IppPrinter + // Delegate to IppPrinter //----------------------- - fun ippRequest(operation: IppOperation, requestedAttributes: List? = null) = - printer.ippRequest(operation, requestedAttributes = requestedAttributes).apply { - operationGroup.attribute("notify-subscription-id", Integer, id) - } - - fun exchange(request: IppRequest) = printer.exchange(request) + protected fun ippRequest(operation: IppOperation, requestedAttributes: List? = null) = + printer.ippRequest(operation, requestedAttributes = requestedAttributes) + .apply { operationGroup.attribute("notify-subscription-id", Integer, id) } //------------------------------------ - // poll and handle event notifications + // Poll and handle event notifications //------------------------------------ var pollHandlesNotifications = false @@ -136,12 +130,12 @@ class IppSubscription( fun expiresAfterDelay() = !leaseDuration.isZero && now().plus(pollEvery).isAfter(expiresAt.minusSeconds(2)) try { pollHandlesNotifications = true - do { + while (pollHandlesNotifications) { if (expired()) log.warning { "subscription #$id has expired" } getNotifications().forEach { handleNotification(it) } if (expiresAfterDelay() && autoRenewSubscription) renew(leaseDuration) Thread.sleep(pollEvery.toMillis()) - } while (pollHandlesNotifications) + } } catch (clientErrorNotFoundException: ClientErrorNotFoundException) { log.info { clientErrorNotFoundException.response!!.statusMessage.toString() } } @@ -151,16 +145,13 @@ class IppSubscription( // Logging // ------- - override fun toString() = StringBuilder("Subscription #$id:").run { - if (hasJobId()) append(" job #$jobId") + override fun objectName() = "Subscription #$id" + + override fun toString() = StringBuilder(objectName()).run { + if (attributes.containsKey("notify-job-id")) append(", job #$jobId") if (attributes.containsKey("notify-events")) append(" events=${events.joinToString(",")}") if (attributes.containsKey("notify-time-interval")) append(" time-interval=$timeInterval") if (attributes.containsKey("notify-lease-duration")) append(" lease-duration=$leaseDuration (expires at $expiresAt)") toString() } - - @JvmOverloads - fun log(logger: Logger, level: Level = INFO) = - attributes.log(logger, level, title = "subscription #$id") - } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index 09a654b7..e46e7112 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -6,6 +6,7 @@ package de.gmuth.ipp.core import java.io.BufferedWriter import java.io.File +import java.net.URI import java.time.Instant import java.time.ZoneOffset import java.time.ZonedDateTime @@ -51,6 +52,9 @@ class IppAttributesGroup(val tag: IppTag) : LinkedHashMap getValues(name: String) = get(name)?.values as T ?: throw IppException("Attribute '$name' not found in group $tag") + fun getUriValue(name: String) = + getValue(name) + fun getTextValue(name: String) = getValue(name).text diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index a29e61a4..1102159a 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -13,7 +13,7 @@ import java.util.logging.Logger.getLogger abstract class IppMessage() { - protected val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) var code: Int? = null // unsigned short (16 bits) var requestId: Int? = null var version: String? = null @@ -40,13 +40,19 @@ abstract class IppMessage() { val operationGroup: IppAttributesGroup get() = getSingleAttributesGroup(Operation) + val printerGroup: IppAttributesGroup // also used for CUPS-Printer-Operation requests + get() = getSingleAttributesGroup(Printer) + val jobGroup: IppAttributesGroup get() = getSingleAttributesGroup(Job) - fun getAttributesGroups(tag: IppTag) = + val subscriptionGroup: IppAttributesGroup + get() = getSingleAttributesGroup(Subscription) + + internal fun getAttributesGroups(tag: IppTag) = attributesGroups.filter { it.tag == tag } - fun getSingleAttributesGroup(tag: IppTag) = getAttributesGroups(tag).run { + internal fun getSingleAttributesGroup(tag: IppTag) = getAttributesGroups(tag).run { if (isEmpty()) throw IppException("No group found with tag '$tag' in $attributesGroups") single() } @@ -168,6 +174,7 @@ abstract class IppMessage() { if (rawBytes == null) "" else " (${rawBytes!!.size} bytes)" ) + @JvmOverloads fun log(logger: Logger, level: Level = INFO, prefix: String = "") { if (rawBytes != null) logger.log(level) { "${prefix}${rawBytes!!.size} raw ipp bytes" } logger.log(level) { "${prefix}version = $version" } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt index 9afcf864..729b7980 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt @@ -8,11 +8,23 @@ import de.gmuth.ipp.core.IppTag.* import java.net.URI import java.nio.charset.Charset import java.time.Duration +import java.util.logging.Level +import java.util.logging.Logger class IppRequest : IppMessage { + private val logger = Logger.getLogger(javaClass.name) - val printerUri: URI - get() = operationGroup.getValueOrNull("printer-uri") ?: throw IppException("Missing 'printer-uri'") + val printerOrJobUri: URI + @SuppressWarnings("kotlin:S1192") + get() = operationGroup.run { + when { + containsKey("printer-uri") -> getUriValue("printer-uri") + containsKey("job-uri") -> getUriValue("job-uri") + else -> throw IppException("Missing 'printer-uri' or 'job-uri' in IppRequest").also { + log(logger, Level.WARNING) + } + } + } override val codeDescription: String get() = operation.toString() @@ -31,7 +43,6 @@ class IppRequest : IppMessage { constructor( operation: IppOperation, printerUri: URI? = null, - jobId: Int? = null, requestedAttributes: Collection? = null, requestingUserName: String? = null, version: String = "2.0", @@ -41,7 +52,6 @@ class IppRequest : IppMessage { ) : super(version, requestId, charset, naturalLanguage) { code = operation.code operationGroup.run { - jobId?.let { attribute("job-id", Integer, it) } printerUri?.let { attribute("printer-uri", Uri, it) } requestedAttributes?.let { attribute("requested-attributes", Keyword, it) } requestingUserName?.let { attribute("requesting-user-name", NameWithoutLanguage, IppString(it)) } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt index b3ceb2c7..43b857fa 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt @@ -22,9 +22,6 @@ class IppResponse : IppMessage { val statusMessage: IppString get() = operationGroup.getValue("status-message") - val printerGroup: IppAttributesGroup - get() = getSingleAttributesGroup(Printer) - val jobGroups: Collection get() = getAttributesGroups(Job) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt index 85e671d3..40733cd8 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt @@ -15,8 +15,8 @@ class IppClientTests { @Test fun sendRequestToURIWithEncodedWhitespaces() { ippClient.ippRequest(GetPrinterAttributes, URI.create("ipp://0/PDF%20Printer")).run { - assertEquals("/PDF%20Printer", printerUri.rawPath) - assertEquals("/PDF Printer", printerUri.path) + assertEquals("/PDF%20Printer", printerOrJobUri.rawPath) + assertEquals("/PDF Printer", printerOrJobUri.path) } } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt index d8c5eff5..96b9677c 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt @@ -16,10 +16,12 @@ class IppExchangeExceptionTests { @Test fun constructor() { - with(IppExchangeException( + with( + IppExchangeException( IppRequest(IppOperation.GetPrinterAttributes).apply { encode() }, null, 400 - )) { + ) + ) { log(log) assertEquals(11, request.code) assertEquals(400, httpStatus) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index e41102ee..22c55168 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -148,8 +148,9 @@ class IppJobTests { remove("job-impressions-completed") remove("number-of-documents") remove("job-printer-uri") + remove("job-uri") } - assertEquals("Job #2366:", toString()) + assertEquals("Job #2366", toString()) } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index 0bf43e9a..7f9d7a1e 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -207,7 +207,7 @@ class IppPrinterTests { @Test fun pause() { - printer.ippClient.basicAuth("user", "password") + printer.basicAuth("user", "password") printer.pause() } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt index 8eed4ba7..da9350eb 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt @@ -26,7 +26,7 @@ class IppRequestTests { assertEquals(null, version) assertEquals(IppOperation.CreateJob, operation) createAttributesGroup(IppTag.Operation) - assertFailsWith { printerUri } + assertFailsWith { printerOrJobUri } } } @@ -38,7 +38,7 @@ class IppRequestTests { assertEquals(IppOperation.StartupPrinter, request.operation) assertEquals(Charsets.UTF_8, request.attributesCharset) assertEquals("en", request.operationGroup.getValue("attributes-natural-language")) - assertEquals("ipp://foo", request.printerUri.toString()) + assertEquals("ipp://foo", request.printerOrJobUri.toString()) assertEquals("Startup-Printer", request.codeDescription) val requestEncoded = request.encode() assertEquals(97, requestEncoded.size) @@ -48,7 +48,7 @@ class IppRequestTests { fun printJobRequest() { val request = IppRequest( IppOperation.PrintJob, URI.create("ipp://printer"), - 0, listOf("one", "two"), "user" + listOf("one", "two"), "user" ) request.documentInputStream = "pdl-content".byteInputStream() log.info { request.toString() } @@ -65,7 +65,7 @@ class IppRequestTests { assertEquals(Charsets.UTF_8, getValue("attributes-charset")) assertEquals("en", getValue("attributes-natural-language")) assertEquals(URI.create("ipp://printer"), getValue("printer-uri")) - assertEquals(0, getValue("job-id")) + //assertEquals(0, getValue("job-id")) assertEquals(listOf("one", "two"), getValues("requested-attributes")) //assertEquals("user".toIppString(), getValue("requesting-user-name")) }