title: How to get notification from printer when physical page/job is complete
body: <p>I'd like to be able to get a notification from a network printer when it physically finishes printing a page (and/or the entire job). This is going to be used in an app I'm writing for print management via web, and since users get charged per-page, and the charge shouldn't go down before the page actually completes.</p>
  <p>I'm not sure whether this requires writing a driver, some kind of plug-in or whether a client app will do. I am flexible with my platform, since my client isn't written yet, so I'd like to hear about any suitable solution in either Windows or Linux, in any programming language/level.</p>
  <p>I'm aware that there is a difference between the spooler and the printer. I'm trying to examine at which level the printer might notify the machine, via IPP, when the page or job physically complete.</p>
  <p>I'm currently looking into Java, using either <a href="http://code.google.com/p/jspi/" rel="nofollow noreferrer">jspi</a> or <a href="http://www.cups4j.org/" rel="nofollow noreferrer">cups4j</a> package to obtain a notification when the IPP property <a href="https://www.rfc-editor.org/rfc/rfc2911#section-4.3.18.2" rel="nofollow noreferrer"><code>job-impressions-completed</code></a>  changes, or alternatively, poll for it. I'm using a CUPS IPP interface to a local printer. Running a simple tester (<code>HelloPrint.java</code> attached below; or the <code>CupsTest.java</code> included in cups4j), I did not receive any <code>job-impressions-completed</code> attribute changes nor did it list the attribute for the job when I was polling.</p>
  <p>So here are the questions:</p>
  <ul>
  <li>Am doing this right? If not, then how should I do it?</li>
  <li>Since this is a CUPS interface to a local printer, it might be that the <code>job-impressions-completed</code> attribute isn't being updated, specifically since it acts as a spooler for the real printer. Assuming that the real printer <em>will</em> notify about or list this attribute, Would this be printer-specific or <em>must</em> any IPP-supporting printer have this attribute available and updated?</li>
  </ul>
  <p>System info: Ubuntu 11.10, CUPS 1.5.0, printer is Brother HL-2240D (<a href="http://welcome.solutions.brother.com/bsc/public_s/id/linux/en/download_prn.html#HL-2240D" rel="nofollow noreferrer">PPD available here</a>)</p>
  <p>Note: HL-2240D is <strong>not</strong> the printer I will be using for the eventual project (specifically, it doesn't support IPP); I'm intending to use either an HP HL4250DN or Samsung 3741ND or similar.</p>
  <p>Here's a sample app using the <code>javax.print</code> packages and jspi:</p>
  <p><strong>HelloPrint.java</strong></p>
  <pre><code>import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.util.Date;
  
  import javax.print.*;
  import javax.print.attribute.*;
  import javax.print.attribute.standard.*;
  import javax.print.event.*;
  
  import de.lohndirekt.print.IppPrintService;
  
  public class HelloPrint {
  
      /**
       * @param args
       */
      public static void main(String[] args) {
          // create request attributes
          PrintRequestAttributeSet requestAttributeSet = new HashPrintRequestAttributeSet();
          requestAttributeSet.add(MediaSizeName.ISO_A4);
          requestAttributeSet.add(new Copies(1));
          requestAttributeSet.add(Sides.DUPLEX);
  
          // find an appropriate service
          // using jspi (http://code.google.com/p/jspi/)
          URI printerURI;
          try {
              printerURI = new URI(&quot;ipp://localhost:631/printers/HL2240D-local&quot;);
          } catch (URISyntaxException e2) {
              e2.printStackTrace();
              return;
          }
          IppPrintService service = new IppPrintService(printerURI);
  
          // by enumerating       
          //      PrintService[] services = PrintServiceLookup.lookupPrintServices(
          //              DocFlavor.INPUT_STREAM.PDF, requestAttributeSet);
          //      for (PrintService service1 : services) {
          //          System.out.println(service1);
          //      }
          //      PrintService service = services[0];
  
          // add listeners to service
          service.addPrintServiceAttributeListener(new PrintServiceAttributeListener() {
              @Override
              public void attributeUpdate(PrintServiceAttributeEvent event) {
                  PrintServiceAttributeSet serviceAttributeSet = event
                          .getAttributes();
                  StringBuilder s = new StringBuilder();
                  s.append(&quot;=== PrintServiceAttributeEvent: (&quot; + serviceAttributeSet.size() + &quot; attributes)\n&quot;);
                  for (Attribute attribute : serviceAttributeSet.toArray()) {
                      PrintServiceAttribute printServiceAttribute = (PrintServiceAttribute) attribute;
  
                      s.append(printServiceAttribute.getCategory().getName()
                              + &quot;/&quot; + printServiceAttribute.getName() + &quot; = &quot;
                              + printServiceAttribute.toString() + &quot;\n&quot;);
  
                  }
                  System.out.println(s.toString());
              }
          });
  
          // add file (blank.pdf is a blank page exported as PDF from LibreOffice
          // Writer)
          FileInputStream inputStream;
          try {
              inputStream = new FileInputStream(&quot;blank.pdf&quot;);
          } catch (FileNotFoundException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
              return;
          }
  
          // create a new doc and job
          DocAttributeSet docAttributeSet = new HashDocAttributeSet();
          docAttributeSet.add(MediaSizeName.ISO_A4);
          docAttributeSet.add(Sides.DUPLEX);
  
          Doc doc = new SimpleDoc(inputStream, DocFlavor.INPUT_STREAM.PDF,
                  docAttributeSet);
  
          DocPrintJob job = service.createPrintJob();
  
          // listen to print job attribute change events
          // attribute set is null, means this means to listen on all dynamic
          // attributes that the job supports.
          job.addPrintJobAttributeListener(new PrintJobAttributeListener() {
              @Override
              public void attributeUpdate(PrintJobAttributeEvent event) {
                  PrintJobAttributeSet jobAttributeSet = event.getAttributes();
                  StringBuilder s = new StringBuilder();
                  s.append(&quot;=== PrintJobAttributeEvent: (&quot; + jobAttributeSet.size() + &quot; attributes)\n&quot;);
                  for (Attribute attribute : jobAttributeSet.toArray()) {
                      PrintJobAttribute jobAttribute = (PrintJobAttribute) attribute;
  
                      s.append(jobAttribute.getCategory().getName() + &quot;/&quot;
                              + jobAttribute.getName() + &quot; = &quot;
                              + jobAttribute.toString() + &quot;\n&quot;);
  
                  }
                  System.out.println(s.toString());
  
              }
          }, null);
  
          // listen to print job events
          job.addPrintJobListener(new PrintJobListener() {
  
              @Override
              public void printJobRequiresAttention(PrintJobEvent pje) {
                  System.out.println(&quot;=== PrintJobEvent: printJobRequiresAttention&quot;);
              }
  
              @Override
              public void printJobNoMoreEvents(PrintJobEvent pje) {
                  // TODO Auto-generated method stub
                  System.out.println(&quot;=== PrintJobEvent: printJobNoMoreEvents&quot;);
                  System.out.println(pje.getPrintEventType());
                  System.out.println(pje.toString());
              }
  
              @Override
              public void printJobFailed(PrintJobEvent pje) {
                  // TODO Auto-generated method stub
                  System.out.println(&quot;=== PrintJobEvent: printJobFailed&quot;);
                  System.out.println(pje.getPrintEventType());
                  System.out.println(pje.toString());
              }
  
              @Override
              public void printJobCompleted(PrintJobEvent pje) {
                  // TODO Auto-generated method stub
                  System.out.println(&quot;=== PrintJobEvent: printJobCompleted&quot;);
                  System.out.println(pje.getPrintEventType());
                  System.out.println(pje.toString());
              }
  
              @Override
              public void printJobCanceled(PrintJobEvent pje) {
                  // TODO Auto-generated method stub
                  System.out.println(&quot;=== PrintJobEvent: printJobCanceled&quot;);
                  System.out.println(pje.getPrintEventType());
                  System.out.println(pje.toString());
              }
  
              @Override
              public void printDataTransferCompleted(PrintJobEvent pje) {
                  System.out.println(&quot;=== PrintJobEvent: printDataTransferCompleted&quot;);
                  System.out.println(pje.getPrintEventType());
                  System.out.println(pje.toString());
              }
          });
  
          // print
          try {
              job.print(doc, requestAttributeSet);
          } catch (PrintException e1) {
              // TODO Auto-generated catch block
              e1.printStackTrace();
              return;
          }
          
          // try polling
          while (true) {
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
                  return;
              }
              
              System.out.println(&quot;=== Polling: I'm alive and it's &quot; + new Date());
              System.out.println(&quot;Job attributes&quot;);
              for (Attribute attribute : job.getAttributes().toArray()) {
                  System.out.println((attribute.getCategory().getName() + &quot;/&quot;
                          + attribute.getName() + &quot; = &quot; + attribute.toString()));
              }
              System.out.println(&quot;Service attributes&quot;);
              for (Attribute attribute : service.getAttributes().toArray()) {
                  System.out.println((attribute.getCategory().getName() + &quot;/&quot;
                          + attribute.getName() + &quot; = &quot; + attribute.toString()));
              }
          }
      }
  
  }
  </code></pre>