2

I need to write a Java method in order to send postscript files to a printer in one job. In other words, I need to reproduce the effect of the following Unix command:

lp -d printer file1.ps file2.ps file3.ps

First I thought I could just concatenate the PS files (using classes like ConcatInputStream and PrintJobWatcher). But the resulting merged PS file is not always valid.

If it helps, here is my current code (I have been asked to do it in Groovy):

/**
 * Prints the {@code files} {@code copyCount} times using 
 * {@code printService}.
 * <p>
 * Exceptions may be thrown.
 * @param printService Print service
 * @param files Groovy array of {@code File} objects
 * @param copyCount Number of copies to print
 */
private static void printJob(
        PrintService printService, 
        def files, 
        int copyCount) {

    // No multiple copy support for PS file, must do it manually
    copyCount.times { i ->

        InputStream inputStream = null
        try {

            log.debug("Create stream for copy #${i}")
            inputStream = new ConcatInputStream()
            for (def file in files) {
                if (file != null) {
                    log.debug("Add '${file.absolutePath}' to the stream")
                    ((ConcatInputStream)inputStream).addInputStream(
                            new FileInputStream(file))
                }
            }

            log.debug("Create document")
            Doc doc = new SimpleDoc(
                    inputStream, DocFlavor.INPUT_STREAM.AUTOSENSE, null)

            log.debug("Create print job")
            DocPrintJob docPrintJob = printService.createPrintJob()

            log.debug("Create watcher")
            PrintJobWatcher watcher = new PrintJobWatcher(docPrintJob)

            log.debug("Print copy #${i}")
            docPrintJob.print(doc, null)

            log.debug("Wait for completion")
            watcher.waitForDone()

        } finally {
            if (inputStream) log.debug("Close the stream")
            inputStream?.close()
        }
    }
}

I’m not allowed to convert the PS into PDF.

I read here that I could insert false 0 startjob pop between the PS files. But then would there be only one job?

I may be confusing the concept of "jobs"...

I didn’t find a post on the topic (sending multiple PS files to the printer in one job). The solution may be so obvious that it blinded me, that why I posted this question.

My next attempt will be to execute lp from the class, even if it looks dirty I know I can make it work that way... If you know a simpler way, please tell me.

Edit:

Executing lp (as below) works well:

/**
 * Prints the {@code files} {@code copyCount} times using an executable.
 * <p>
 * Exceptions may be thrown.
 * @param config ConfigObject containing closures for building the
 *      command line to the printing executable, and to analyze the
 *      return code. Example of config file:
 * 
 * print {
 *      commandClosure = { printerName, files -> 
 *          [
 *              'lp', 
 *              '-d', printerName, 
 *              files.collect{ it.absolutePath }
 *          ].flatten()
 *      }
 *      errorClosure = { returnCode, stdout, stderr -> returnCode != 0 }
 *      warnClosure = { returnCode, stdout, stderr -> 
 *          !stderr?.isAllWhitespace() }
 *  }
 * 
 * @param printerName Printer name
 * @param files Groovy array of {@code File} objects
 * @param copyCount Number of copies to print
 */
private static void printJob(
        ConfigObject config,
        String printerName, 
        def files, 
        int copyCount) {

    files.removeAll([null])

    Integer copyCount = job.copyCountString.toInteger()
    copyCount.times { i ->

        def command = config.print.commandClosure(printerName, files)
        log.debug("Command: `" + command.join(' ') + "`")

        def proc = command.execute()
        proc.waitFor()

        def returnCode = proc.exitValue()
        def stdout = proc.in.text
        def stderr = proc.err.text
        def debugString = "`" + command.join(' ') + 
                    "`\nReturn code: " + returnCode + 
                    "\nSTDOUT:\n" + stdout + "\nSTDERR:\n" + stderr

        if (config.print.errorClosure(returnCode, stdout, stderr)) {
            log.error("Error while calling ${debugString}")
            throw new PrintException("Error while calling ${debugString}")
        } else if (config.print.warnClosure(returnCode, stdout, stderr)) {
            log.warn("Warnings while calling ${debugString}")
        } else {
            log.debug("Command successful ${debugString}")
        }

    }
}

Even if I would prefer not to use an external executable... This issue is not anymore critical for me. I will accept an answer if it does not require the call to an external executable.

Community
  • 1
  • 1
boumbh
  • 2,010
  • 1
  • 19
  • 21
  • Not sure this could work by just concatenating the files together... Won't links to pages be out of step for the 2nd, 3rd, etc files? – tim_yates Aug 08 '13 at 10:10
  • Does `"lp -d printer file1.ps file2.ps file3.ps".execute()` work ;-) – tim_yates Aug 08 '13 at 10:11
  • Hi, that’s what I’m implementing now. I think it will work. I’ll put feedbacks when I’m done. – boumbh Aug 08 '13 at 10:38
  • Added an answer that just loops through the files and prints each one in turn... Does that work? – tim_yates Aug 08 '13 at 10:58
  • Added an improvement to your workaround in my answer below. If `command.execute()` outputs a lot of text, your above code could lock up :-( – tim_yates Aug 08 '13 at 11:54

2 Answers2

1

Actually, can't you just loop through the files inside your loop for the number of copies?

ie:

private static void printJob( PrintService printService, def files, int copyCount) {
    // No multiple copy support for PS file, must do it manually
    copyCount.times { i ->
        log.debug( "Printing Copy $i" )
        files.each { file ->
            log.debug( "Printing $file" )
            file.withInputStream { fis ->
                Doc doc = new SimpleDoc( fis, DocFlavor.INPUT_STREAM.AUTOSENSE, null )
                DocPrintJob docPrintJob = printService.createPrintJob()
                PrintJobWatcher watcher = new PrintJobWatcher( docPrintJob )
                docPrintJob.print( doc, null )
                watcher.waitForDone()
            }
        }
    }
}

(untested)

edit

As an update to your workaround above, rather than:

    def proc = command.execute()
    proc.waitFor()

    def returnCode = proc.exitValue()
    def stdout = proc.in.text
    def stderr = proc.err.text

You're probably better with:

    def proc = command.execute()
    def out = new StringWriter()
    def err = new StringWriter()
    ps.consumeProcessOutput( out, err )
    ps.waitFor()

    def returnCode = proc.exitValue()
    def stdout = out.toString()
    def stderr = err.toString()

As this won't block if the process writes a lot of information :-)

tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • Thanks for your time. This would create a job for each postscript file, I need to group the postscript files into a single job. – boumbh Aug 08 '13 at 11:31
  • Ah yeah, this is an issue... You can only call `docPrintJob.print` once :-/ – tim_yates Aug 08 '13 at 11:49
0

One of the issues could be related to the Document Structure Convention (DSC) comments. These comments provide metadata about the document contained in the file. A tool like ghostscript should be able to process the resulting concatenated file because it ignores DSC comments entirely and just processes the postscript. But tools that expect to work on DSC-conforming files will be confused when the first file ends (it's marked by an End comment) and there's more data in the file.

One thing that might work is to strip all comments from the files, so there's no misleading DSC information. (DSC comments will always be a full line starting with %%, so an RE substitution should do it. s/^%[^$]*$//g)

luser droog
  • 18,988
  • 3
  • 53
  • 105