Back to index

system-config-printer  1.3.9+20120706
monitor.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## Copyright (C) 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00004 ## Author: Tim Waugh <twaugh@redhat.com>
00005 
00006 ## This program is free software; you can redistribute it and/or modify
00007 ## it under the terms of the GNU General Public License as published by
00008 ## the Free Software Foundation; either version 2 of the License, or
00009 ## (at your option) any later version.
00010 
00011 ## This program is distributed in the hope that it will be useful,
00012 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014 ## GNU General Public License for more details.
00015 
00016 ## You should have received a copy of the GNU General Public License
00017 ## along with this program; if not, write to the Free Software
00018 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00019 
00020 import cups
00021 cups.require("1.9.50")
00022 import dbus
00023 import dbus.glib
00024 import gobject
00025 import time
00026 from debug import *
00027 import pprint
00028 from gettext import gettext as _
00029 import ppdcache
00030 import statereason
00031 from statereason import StateReason
00032 
00033 CONNECTING_TIMEOUT = 60 # seconds
00034 MIN_REFRESH_INTERVAL = 1 # seconds
00035 
00036 def state_reason_is_harmless (reason):
00037     if (reason.startswith ("moving-to-paused") or
00038         reason.startswith ("paused") or
00039         reason.startswith ("shutdown") or
00040         reason.startswith ("stopping") or
00041         reason.startswith ("stopped-partly")):
00042         return True
00043     return False
00044 
00045 def collect_printer_state_reasons (connection, ppdcache):
00046     result = {}
00047     try:
00048         printers = connection.getPrinters ()
00049     except cups.IPPError:
00050         return result
00051 
00052     for name, printer in printers.iteritems ():
00053         reasons = printer["printer-state-reasons"]
00054         for reason in reasons:
00055             if reason == "none":
00056                 break
00057             if state_reason_is_harmless (reason):
00058                 continue
00059             if not result.has_key (name):
00060                 result[name] = []
00061             result[name].append (StateReason (name, reason, ppdcache))
00062     return result
00063 
00064 class Monitor(gobject.GObject):
00065     __gsignals__ = {
00066         'refresh':               (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00067                                   []),
00068         'monitor-exited' :       (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00069                                   []),
00070         'state-reason-added':    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00071                                   [gobject.TYPE_PYOBJECT]),
00072         'state-reason-removed':  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00073                                   [gobject.TYPE_PYOBJECT]),
00074         'still-connecting':      (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00075                                   [gobject.TYPE_PYOBJECT]),
00076         'now-connected':         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00077                                   [gobject.TYPE_STRING]),
00078         'job-added':             (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00079                                   [gobject.TYPE_INT, gobject.TYPE_STRING,
00080                                    gobject.TYPE_PYOBJECT,
00081                                    gobject.TYPE_PYOBJECT]),
00082         'job-event':             (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00083                                   [gobject.TYPE_INT, gobject.TYPE_STRING,
00084                                    gobject.TYPE_PYOBJECT,
00085                                    gobject.TYPE_PYOBJECT]),
00086         'job-removed':           (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00087                                   [gobject.TYPE_INT, gobject.TYPE_STRING,
00088                                    gobject.TYPE_PYOBJECT]),
00089         'printer-added':         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00090                                   [gobject.TYPE_STRING]),
00091         'printer-event':         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00092                                   [gobject.TYPE_STRING, gobject.TYPE_STRING,
00093                                    gobject.TYPE_PYOBJECT]),
00094         'printer-removed':       (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00095                                   [gobject.TYPE_STRING]),
00096         'cups-connection-error': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00097                                   []),
00098         'cups-connection-recovered': (gobject.SIGNAL_RUN_LAST,
00099                                       gobject.TYPE_NONE, []),
00100         'cups-ipp-error':        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00101                                   [gobject.TYPE_INT, gobject.TYPE_STRING])
00102         }
00103 
00104     # Monitor jobs and printers.
00105     DBUS_PATH="/com/redhat/PrinterSpooler"
00106     DBUS_IFACE="com.redhat.PrinterSpooler"
00107 
00108     def __init__(self, bus=None, my_jobs=True,
00109                  specific_dests=None, monitor_jobs=True, host=None,
00110                  port=None, encryption=None):
00111         gobject.GObject.__init__ (self)
00112         self.my_jobs = my_jobs
00113         self.specific_dests = specific_dests
00114         self.monitor_jobs = monitor_jobs
00115         self.jobs = {}
00116         self.printer_state_reasons = {}
00117         self.printers = set()
00118         self.process_pending_events = True
00119         self.fetch_jobs_timer = None
00120         self.cups_connection_in_error = False
00121 
00122         if host:
00123             cups.setServer (host)
00124         if port:
00125             cups.setPort (port)
00126         if encryption:
00127             cups.setEncryption (encryption)
00128         self.user = cups.getUser ()
00129         self.host = cups.getServer ()
00130         self.port = cups.getPort ()
00131         self.encryption = cups.getEncryption ()
00132         self.ppdcache = ppdcache.PPDCache (host=self.host,
00133                                            port=self.port,
00134                                            encryption=self.encryption)
00135 
00136         self.which_jobs = "not-completed"
00137         self.reasons_seen = {}
00138         self.connecting_timers = {}
00139         self.still_connecting = set()
00140         self.connecting_to_device = {}
00141         self.received_any_dbus_signals = False
00142         self.update_timer = None
00143 
00144         if bus == None:
00145             try:
00146                 bus = dbus.SystemBus ()
00147             except dbus.exceptions.DBusException:
00148                 # System bus not running.
00149                 pass
00150 
00151         self.bus = bus
00152         if bus != None:
00153             bus.add_signal_receiver (self.handle_dbus_signal,
00154                                      path=self.DBUS_PATH,
00155                                      dbus_interface=self.DBUS_IFACE)
00156         self.sub_id = -1
00157 
00158     def get_printers (self):
00159         return self.printers.copy ()
00160 
00161     def get_jobs (self):
00162         return self.jobs.copy ()
00163 
00164     def get_ppdcache (self):
00165         return self.ppdcache
00166 
00167     def cleanup (self):
00168         if self.sub_id != -1:
00169             user = cups.getUser ()
00170             try:
00171                 cups.setUser (self.user)
00172                 c = cups.Connection (host=self.host,
00173                                      port=self.port,
00174                                      encryption=self.encryption)
00175                 c.cancelSubscription (self.sub_id)
00176                 debugprint ("Canceled subscription %d" % self.sub_id)
00177             except:
00178                 pass
00179             cups.setUser (user)
00180 
00181         if self.bus != None:
00182             self.bus.remove_signal_receiver (self.handle_dbus_signal,
00183                                              path=self.DBUS_PATH,
00184                                              dbus_interface=self.DBUS_IFACE)
00185 
00186         timers = self.connecting_timers.values ()
00187         for timer in [self.update_timer, self.fetch_jobs_timer]:
00188             if timer:
00189                 timers.append (timer)
00190         for timer in timers:
00191             gobject.source_remove (timer)
00192 
00193         self.emit ('monitor-exited')
00194 
00195     def set_process_pending (self, whether):
00196         self.process_pending_events = whether
00197 
00198     def check_still_connecting(self, printer):
00199         """Timer callback to check on connecting-to-device reasons."""
00200         if not self.process_pending_events:
00201             # Defer the timer by setting a new one.
00202             timer = gobject.timeout_add (200, self.check_still_connecting,
00203                                          printer)
00204             self.connecting_timers[printer] = timer
00205             return False
00206 
00207         if self.connecting_timers.has_key (printer):
00208             del self.connecting_timers[printer]
00209 
00210         debugprint ("Still-connecting timer fired for `%s'" % printer)
00211         (printer_jobs, my_printers) = self.sort_jobs_by_printer ()
00212         self.update_connecting_devices (printer_jobs)
00213 
00214         # Don't run this callback again.
00215         return False
00216 
00217     def update_connecting_devices(self, printer_jobs={}):
00218         """Updates connecting_to_device dict and still_connecting set."""
00219         time_now = time.time ()
00220         connecting_to_device = {}
00221         trouble = False
00222         for printer, reasons in self.printer_state_reasons.iteritems ():
00223             connected = True
00224             for reason in reasons:
00225                 if reason.get_reason () == "connecting-to-device":
00226                     have_processing_job = False
00227                     for job, data in \
00228                             printer_jobs.get (printer, {}).iteritems ():
00229                         state = data.get ('job-state',
00230                                           cups.IPP_JOB_CANCELED)
00231                         if state == cups.IPP_JOB_PROCESSING:
00232                             have_processing_job = True
00233                             break
00234 
00235                     if not have_processing_job:
00236                         debugprint ("Ignoring stale connecting-to-device x")
00237                         continue
00238 
00239                     # Build a new connecting_to_device dict.  If our existing
00240                     # dict already has an entry for this printer, use that.
00241                     printer = reason.get_printer ()
00242                     t = self.connecting_to_device.get (printer, time_now)
00243                     connecting_to_device[printer] = t
00244                     debugprint ("Connecting time: %d" % (time_now - t))
00245                     if time_now - t >= CONNECTING_TIMEOUT:
00246                         if have_processing_job:
00247                             if printer not in self.still_connecting:
00248                                 self.still_connecting.add (printer)
00249                                 self.emit ('still-connecting', reason)
00250                             if self.connecting_timers.has_key (printer):
00251                                 gobject.source_remove (self.connecting_timers
00252                                                        [printer])
00253                                 del self.connecting_timers[printer]
00254                                 debugprint ("Stopped connecting timer "
00255                                             "for `%s'" % printer)
00256 
00257                     connected = False
00258                     break
00259 
00260             if connected and self.connecting_timers.has_key (printer):
00261                 gobject.source_remove (self.connecting_timers[printer])
00262                 del self.connecting_timers[printer]
00263                 debugprint ("Stopped connecting timer for `%s'" % printer)
00264 
00265         # Clear any previously-notified errors that are now fine.
00266         remove = set()
00267         for printer in self.still_connecting:
00268             if not connecting_to_device.has_key (printer):
00269                 remove.add (printer)
00270                 self.emit ('now-connected', printer)
00271                 if self.connecting_timers.has_key (printer):
00272                     gobject.source_remove (self.connecting_timers[printer])
00273                     del self.connecting_timers[printer]
00274                     debugprint ("Stopped connecting timer for `%s'" % printer)
00275 
00276         self.still_connecting = self.still_connecting.difference (remove)
00277         self.connecting_to_device = connecting_to_device
00278 
00279     def check_state_reasons(self, my_printers=set(), printer_jobs={}):
00280         # Look for any new reasons since we last checked.
00281         old_reasons_seen_keys = self.reasons_seen.keys ()
00282         reasons_now = set()
00283         for printer, reasons in self.printer_state_reasons.iteritems ():
00284             for reason in reasons:
00285                 tuple = reason.get_tuple ()
00286                 printer = reason.get_printer ()
00287                 reasons_now.add (tuple)
00288                 if not self.reasons_seen.has_key (tuple):
00289                     # New reason.
00290                     self.emit ('state-reason-added', reason)
00291                     self.reasons_seen[tuple] = reason
00292 
00293                 if (reason.get_reason () == "connecting-to-device" and
00294                     not self.connecting_to_device.has_key (printer)):
00295                     # First time we've seen this.
00296 
00297                     have_processing_job = False
00298                     for job, data in \
00299                             printer_jobs.get (printer, {}).iteritems ():
00300                         state = data.get ('job-state',
00301                                           cups.IPP_JOB_CANCELED)
00302                         if state == cups.IPP_JOB_PROCESSING:
00303                             have_processing_job = True
00304                             break
00305 
00306                     if have_processing_job:
00307                         t = gobject.timeout_add_seconds (
00308                             (1 + CONNECTING_TIMEOUT),
00309                             self.check_still_connecting,
00310                             printer)
00311                         self.connecting_timers[printer] = t
00312                         debugprint ("Start connecting timer for `%s'" %
00313                                     printer)
00314                     else:
00315                         # Don't notify about this, as it must be stale.
00316                         debugprint ("Ignoring stale connecting-to-device")
00317                         if get_debugging ():
00318                             debugprint (pprint.pformat (printer_jobs))
00319 
00320         self.update_connecting_devices (printer_jobs)
00321         items = self.reasons_seen.keys ()
00322         for tuple in items:
00323             if not tuple in reasons_now:
00324                 # Reason no longer present.
00325                 reason = self.reasons_seen[tuple]
00326                 del self.reasons_seen[tuple]
00327                 self.emit ('state-reason-removed', reason)
00328 
00329     def get_notifications(self):
00330         if not self.process_pending_events:
00331             # Defer the timer callback.
00332             if self.update_timer:
00333                 gobject.source_remove (self.update_timer)
00334 
00335             self.update_timer = gobject.timeout_add (200,
00336                                                      self.get_notifications)
00337             debugprint ("Deferred get_notifications by 200ms")
00338             return False
00339 
00340         debugprint ("get_notifications")
00341         user = cups.getUser ()
00342         try:
00343             cups.setUser (self.user)
00344             c = cups.Connection (host=self.host,
00345                                  port=self.port,
00346                                  encryption=self.encryption)
00347 
00348             try:
00349                 try:
00350                     notifications = c.getNotifications ([self.sub_id],
00351                                                         [self.sub_seq + 1])
00352                 except AttributeError:
00353                     notifications = c.getNotifications ([self.sub_id])
00354             except cups.IPPError, (e, m):
00355                 cups.setUser (user)
00356                 if e == cups.IPP_NOT_FOUND:
00357                     # Subscription lease has expired.
00358                     self.sub_id = -1
00359                     debugprint ("Subscription not found, will refresh")
00360                     self.refresh ()
00361                     return False
00362 
00363                 self.emit ('cups-ipp-error', e, m)
00364                 if e == cups.IPP_FORBIDDEN:
00365                     return False
00366 
00367                 debugprint ("getNotifications failed with %d (%s)" % (e, m))
00368                 return True
00369         except RuntimeError:
00370             cups.setUser (user)
00371             debugprint ("cups-connection-error, will retry")
00372             self.cups_connection_in_error = True
00373             self.emit ('cups-connection-error')
00374             return True
00375 
00376         if self.cups_connection_in_error:
00377             self.cups_connection_in_error = False
00378             debugprint ("cups-connection-recovered")
00379             self.emit ('cups-connection-recovered')
00380 
00381         cups.setUser (user)
00382         jobs = self.jobs.copy ()
00383         for event in notifications['events']:
00384             seq = event['notify-sequence-number']
00385             self.sub_seq = seq
00386             nse = event['notify-subscribed-event']
00387             debugprint ("%d %s %s" % (seq, nse, event['notify-text']))
00388             if get_debugging ():
00389                 debugprint (pprint.pformat (event))
00390             if nse.startswith ('printer-'):
00391                 # Printer events
00392                 name = event['printer-name']
00393                 if nse == 'printer-added' and name not in self.printers:
00394                     self.printers.add (name)
00395                     self.emit ('printer-added', name)
00396 
00397                 elif nse == 'printer-deleted' and name in self.printers:
00398                     self.printers.remove (name)
00399                     items = self.reasons_seen.keys ()
00400                     for tuple in items:
00401                         if tuple[1] == name:
00402                             reason = self.reasons_seen[tuple]
00403                             del self.reasons_seen[tuple]
00404                             self.emit ('state-reason-removed', reason)
00405                             
00406                     if self.printer_state_reasons.has_key (name):
00407                         del self.printer_state_reasons[name]
00408 
00409                     self.emit ('printer-removed', name)
00410                 elif name in self.printers:
00411                     printer_state_reasons = event['printer-state-reasons']
00412                     reasons = []
00413                     for reason in printer_state_reasons:
00414                         if reason == "none":
00415                             break
00416                         if state_reason_is_harmless (reason):
00417                             continue
00418                         reasons.append (StateReason (name, reason,
00419                                                      self.ppdcache))
00420                     self.printer_state_reasons[name] = reasons
00421 
00422                     self.emit ('printer-event', name, nse, event)
00423                 continue
00424 
00425             # Job events
00426             if not nse.startswith ("job-"):
00427                 # Some versions of CUPS give empty
00428                 # notify-subscribed-event attributes (STR #3608).
00429                 debugprint ("Unhandled nse %s" % repr (nse))
00430                 continue
00431 
00432             jobid = event['notify-job-id']
00433             if (nse == 'job-created' or
00434                 (nse == 'job-state-changed' and
00435                  not jobs.has_key (jobid) and
00436                  event['job-state'] == cups.IPP_JOB_PROCESSING)):
00437                 if (self.specific_dests != None and
00438                     event['printer-name'] not in self.specific_dests):
00439                     continue
00440 
00441                 try:
00442                     attrs = c.getJobAttributes (jobid)
00443                     if (self.my_jobs and
00444                         attrs['job-originating-user-name'] != cups.getUser ()):
00445                         continue
00446 
00447                     jobs[jobid] = attrs
00448                 except KeyError:
00449                     jobs[jobid] = {'job-k-octets': 0}
00450                 except cups.IPPError, (e, m):
00451                     self.emit ('cups-ipp-error', e, m)
00452                     jobs[jobid] = {'job-k-octets': 0}
00453 
00454                 self.emit ('job-added', jobid, nse, event, jobs[jobid].copy ())
00455             elif (nse == 'job-completed' or
00456                   (nse == 'job-state-changed' and
00457                    event['job-state'] == cups.IPP_JOB_COMPLETED)):
00458                 if not (self.which_jobs in ['completed', 'all']):
00459                     try:
00460                         del jobs[jobid]
00461                         self.emit ('job-removed', jobid, nse, event)
00462                     except KeyError:
00463                         pass
00464                     continue
00465 
00466             try:
00467                 job = jobs[jobid]
00468             except KeyError:
00469                 continue
00470 
00471             for attribute in ['job-state',
00472                               'job-name']:
00473                 job[attribute] = event[attribute]
00474             if event.has_key ('notify-printer-uri'):
00475                 job['job-printer-uri'] = event['notify-printer-uri']
00476 
00477             self.emit ('job-event', jobid, nse, event, job.copy ())
00478 
00479         self.set_process_pending (False)
00480         self.update_jobs (jobs)
00481         self.jobs = jobs
00482         self.set_process_pending (True)
00483 
00484         # Update again when we're told to.  If we're getting CUPS
00485         # D-Bus signals, however, rely on those instead.
00486         if not self.received_any_dbus_signals:
00487             if self.update_timer:
00488                 gobject.source_remove (self.update_timer)
00489 
00490             interval = notifications['notify-get-interval']
00491             t = gobject.timeout_add_seconds (interval,
00492                                              self.get_notifications)
00493             debugprint ("Next notifications fetch in %ds" % interval)
00494             self.update_timer = t
00495 
00496         return False
00497 
00498     def refresh(self, which_jobs=None, refresh_all=True):
00499         debugprint ("refresh")
00500 
00501         self.emit ('refresh')
00502         if which_jobs != None:
00503             self.which_jobs = which_jobs
00504 
00505         user = cups.getUser ()
00506         try:
00507             cups.setUser (self.user)
00508             c = cups.Connection (host=self.host,
00509                                  port=self.port,
00510                                  encryption=self.encryption)
00511         except RuntimeError:
00512             self.emit ('cups-connection-error')
00513             cups.setUser (user)
00514             return
00515 
00516         if self.sub_id != -1:
00517             try:
00518                 c.cancelSubscription (self.sub_id)
00519             except cups.IPPError, (e, m):
00520                 self.emit ('cups-ipp-error', e, m)
00521 
00522             if self.update_timer:
00523                 gobject.source_remove (self.update_timer)
00524 
00525             debugprint ("Canceled subscription %d" % self.sub_id)
00526 
00527         try:
00528             del self.sub_seq
00529         except AttributeError:
00530             pass
00531 
00532         events = ["printer-added",
00533                   "printer-deleted",
00534                   "printer-state-changed"]
00535         if self.monitor_jobs:
00536             events.extend (["job-created",
00537                             "job-completed",
00538                             "job-stopped",
00539                             "job-state-changed",
00540                             "job-progress"])
00541 
00542         try:
00543             self.sub_id = c.createSubscription ("/", events=events)
00544             debugprint ("Created subscription %d, events=%s" % (self.sub_id,
00545                                                                 repr (events)))
00546         except cups.IPPError, (e, m):
00547             self.emit ('cups-ipp-error', e, m)
00548 
00549         cups.setUser (user)
00550 
00551         if self.sub_id != -1:
00552             self.update_timer = gobject.timeout_add_seconds (
00553                 MIN_REFRESH_INTERVAL,
00554                 self.get_notifications)
00555             debugprint ("Next notifications fetch in %ds" %
00556                         MIN_REFRESH_INTERVAL)
00557 
00558         if self.monitor_jobs:
00559             jobs = self.jobs.copy ()
00560             if self.which_jobs not in ['all', 'completed']:
00561                 # Filter out completed jobs.
00562                 filtered = {}
00563                 for jobid, job in jobs.iteritems ():
00564                     if job.get ('job-state',
00565                                 cups.IPP_JOB_CANCELED) < cups.IPP_JOB_CANCELED:
00566                         filtered[jobid] = job
00567                 jobs = filtered
00568 
00569             self.fetch_first_job_id = 1
00570             if self.fetch_jobs_timer:
00571                 gobject.source_remove (self.fetch_jobs_timer)
00572             self.fetch_jobs_timer = gobject.timeout_add (5, self.fetch_jobs,
00573                                                          refresh_all)
00574         else:
00575             jobs = {}
00576 
00577         try:
00578             r = collect_printer_state_reasons (c, self.ppdcache)
00579             self.printer_state_reasons = r
00580             dests = c.getPrinters ()
00581             self.printers = set(dests.keys ())
00582         except cups.IPPError, (e, m):
00583             self.emit ('cups-ipp-error', e, m)
00584             return
00585         except RuntimeError:
00586             self.emit ('cups-connection-error')
00587             return
00588 
00589         if self.specific_dests != None:
00590             for jobid in jobs.keys ():
00591                 uri = jobs[jobid].get('job-printer-uri', '/')
00592                 i = uri.rfind ('/')
00593                 printer = uri[i + 1:]
00594                 if printer not in self.specific_dests:
00595                     del jobs[jobid]
00596 
00597         self.set_process_pending (False)
00598         for printer in self.printers:
00599             self.emit ('printer-added', printer)
00600         for jobid, job in jobs.iteritems ():
00601             self.emit ('job-added', jobid, '', {}, job)
00602         self.update_jobs (jobs)
00603         self.jobs = jobs
00604         self.set_process_pending (True)
00605         return False
00606 
00607     def fetch_jobs (self, refresh_all):
00608         if not self.process_pending_events:
00609             # Skip this call.  We'll get called again soon.
00610             return True
00611 
00612         user = cups.getUser ()
00613         try:
00614             cups.setUser (self.user)
00615             c = cups.Connection (host=self.host,
00616                                  port=self.port,
00617                                  encryption=self.encryption)
00618         except RuntimeError:
00619             self.emit ('cups-connection-error')
00620             self.fetch_jobs_timer = None
00621             cups.setUser (user)
00622             return False
00623 
00624         limit = 1
00625         r = ["job-id",
00626              "job-printer-uri",
00627              "job-state",
00628              "job-originating-user-name",
00629              "job-k-octets",
00630              "job-name",
00631              "time-at-creation"]
00632         try:
00633             fetched = c.getJobs (which_jobs=self.which_jobs,
00634                                  my_jobs=self.my_jobs,
00635                                  first_job_id=self.fetch_first_job_id,
00636                                  limit=limit,
00637                                  requested_attributes=r)
00638         except cups.IPPError, (e, m):
00639             self.emit ('cups-ipp-error', e, m)
00640             self.fetch_jobs_timer = None
00641             cups.setUser (user)
00642             return False
00643 
00644         cups.setUser (user)
00645         got = len (fetched)
00646         debugprint ("Got %s jobs, asked for %s" % (got, limit))
00647 
00648         jobs = self.jobs.copy ()
00649         jobids = fetched.keys ()
00650         jobids.sort ()
00651         if got > 0:
00652             last_jobid = jobids[got - 1]
00653             if last_jobid < self.fetch_first_job_id:
00654                 last_jobid = self.fetch_first_job_id + limit - 1
00655                 debugprint ("Unexpected job IDs returned: %s" % repr (jobids))
00656                 debugprint ("That's not what we asked for!")
00657         else:
00658             last_jobid = self.fetch_first_job_id + limit - 1
00659         for jobid in xrange (self.fetch_first_job_id, last_jobid + 1):
00660             try:
00661                 job = fetched[jobid]
00662                 if self.specific_dests != None:
00663                     uri = job.get('job-printer-uri', '/')
00664                     i = uri.rfind ('/')
00665                     printer = uri[i + 1:]
00666                     if printer not in self.specific_dests:
00667                         raise KeyError
00668 
00669                 if jobs.has_key (jobid):
00670                     n = 'job-event'
00671                 else:
00672                     n = 'job-added'
00673 
00674                 jobs[jobid] = job
00675                 self.emit (n, jobid, '', {}, job.copy ())
00676             except KeyError:
00677                 # No job by that ID.
00678                 if jobs.has_key (jobid):
00679                     del jobs[jobid]
00680                     self.emit ('job-removed', jobid, '', {})
00681 
00682         jobids = jobs.keys ()
00683         jobids.sort ()
00684         if got < limit:
00685             trim = False
00686             for i in range (len (jobids)):
00687                 jobid = jobids[i]
00688                 if not trim and jobid > last_jobid:
00689                     trim = True
00690             
00691                 if trim:
00692                     del jobs[jobid]
00693                     self.emit ('job-removed', jobid, '', {})
00694 
00695         self.update_jobs (jobs)
00696         self.jobs = jobs
00697 
00698         if got < limit:
00699             # That's all.  Don't run this timer again.
00700             self.fetch_jobs_timer = None
00701             return False
00702 
00703         # Remember where we got up to and run this timer again.
00704         next = jobid + 1
00705 
00706         while not refresh_all and self.jobs.has_key (next):
00707             next += 1
00708 
00709         self.fetch_first_job_id = next
00710         return True
00711 
00712     def sort_jobs_by_printer (self, jobs=None):
00713         if jobs == None:
00714             jobs = self.jobs
00715 
00716         my_printers = set()
00717         printer_jobs = {}
00718         for job, data in jobs.iteritems ():
00719             state = data.get ('job-state', cups.IPP_JOB_CANCELED)
00720             if state >= cups.IPP_JOB_CANCELED:
00721                 continue
00722             uri = data.get ('job-printer-uri', '')
00723             i = uri.rfind ('/')
00724             if i == -1:
00725                 continue
00726             printer = uri[i + 1:]
00727             my_printers.add (printer)
00728             if not printer_jobs.has_key (printer):
00729                 printer_jobs[printer] = {}
00730             printer_jobs[printer][job] = data
00731 
00732         return (printer_jobs, my_printers)
00733 
00734     def update_jobs(self, jobs):
00735         debugprint ("update_jobs")
00736         (printer_jobs, my_printers) = self.sort_jobs_by_printer (jobs)
00737         self.check_state_reasons (my_printers, printer_jobs)
00738 
00739     def update(self):
00740         if self.update_timer:
00741             gobject.source_remove (self.update_timer)
00742 
00743         self.update_timer = gobject.timeout_add (200, self.get_notifications)
00744         debugprint ("Next notifications fetch in 200ms (update called)")
00745 
00746     def handle_dbus_signal(self, *args):
00747         debugprint ("D-Bus signal from CUPS... calling update")
00748         self.update ()
00749         if not self.received_any_dbus_signals:
00750             self.received_any_dbus_signals = True
00751 
00752 gobject.type_register (Monitor)
00753 
00754 if __name__ == '__main__':
00755     class SignalWatcher:
00756         def __init__ (self, monitor):
00757             monitor.connect ('monitor-exited', self.on_monitor_exited)
00758             monitor.connect ('state-reason-added', self.on_state_reason_added)
00759             monitor.connect ('state-reason-removed',
00760                              self.on_state_reason_removed)
00761             monitor.connect ('still-connecting', self.on_still_connecting)
00762             monitor.connect ('now-connected', self.on_now_connected)
00763             monitor.connect ('job-added', self.on_job_added)
00764             monitor.connect ('job-event', self.on_job_event)
00765             monitor.connect ('job-removed', self.on_job_removed)
00766             monitor.connect ('printer-added', self.on_printer_added)
00767             monitor.connect ('printer-event', self.on_printer_event)
00768             monitor.connect ('printer-removed', self.on_printer_removed)
00769             monitor.connect ('cups-connection-error',
00770                              self.on_cups_connection_error)
00771             monitor.connect ('cups-ipp-error', self.on_cups_ipp_error)
00772 
00773         def on_monitor_exited (self, obj):
00774             print "*%s: monitor exited" % obj
00775 
00776         def on_state_reason_added (self, obj, reason):
00777             print "*%s: +%s" % (obj, reason)
00778 
00779         def on_state_reason_removed (self, obj, reason):
00780             print "*%s: -%s" % (obj, reason)
00781 
00782         def on_still_connecting (self, obj, reason):
00783             print "*%s: still connecting: %s" % (obj, reason)
00784 
00785         def on_now_connected (self, obj, printer):
00786             print "*%s: now connected: %s" % (obj, printer)
00787 
00788         def on_job_added (self, obj, jobid, eventname, event, jobdata):
00789             print "*%s: job %d added" % (obj, jobid)
00790 
00791         def on_job_event (self, obj, jobid, eventname, event, jobdata):
00792             print "*%s: job %d event: %s" % (obj, jobid, event)
00793 
00794         def on_job_removed (self, obj, jobid, eventname, event):
00795             print "*%s: job %d removed (%s)"% (obj, jobid, eventname)
00796 
00797         def on_printer_added (self, obj, name):
00798             print "*%s: printer added: %s" % (obj, name)
00799 
00800         def on_printer_event (self, obj, name, eventname, event):
00801             print "*%s: printer event: %s: %s" % (obj, name, eventname)
00802 
00803         def on_printer_removed (self, obj, name):
00804             print "*%s: printer %s removed" % (obj, name)
00805 
00806         def on_cups_connection_error (self, obj):
00807             print "*%s: cups connection error" % obj
00808 
00809         def on_cups_ipp_error (self, obj, err, errstring):
00810             print "*%s: IPP error (%d): %s" % (obj, err, errstring)
00811 
00812     set_debugging (True)
00813     m = Monitor ()
00814     SignalWatcher (m)
00815     m.refresh ()
00816     loop = gobject.MainLoop ()
00817     try:
00818         loop.run ()
00819     finally:
00820         m.cleanup ()