Back to index

system-config-printer  1.3.9+20120706
asyncpk1.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## Copyright (C) 2007, 2008, 2009, 2010 Red Hat, Inc.
00004 ## Copyright (C) 2008 Novell, Inc.
00005 ## Authors: Tim Waugh <twaugh@redhat.com>, Vincent Untz
00006 
00007 ## This program is free software; you can redistribute it and/or modify
00008 ## it under the terms of the GNU General Public License as published by
00009 ## the Free Software Foundation; either version 2 of the License, or
00010 ## (at your option) any later version.
00011 
00012 ## This program is distributed in the hope that it will be useful,
00013 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 ## GNU General Public License for more details.
00016 
00017 ## You should have received a copy of the GNU General Public License
00018 ## along with this program; if not, write to the Free Software
00019 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00020 
00021 import cups
00022 import dbus
00023 import gobject
00024 import gtk
00025 import os
00026 import sys
00027 import tempfile
00028 import xml.etree.ElementTree
00029 
00030 import asyncipp
00031 from debug import *
00032 import debug
00033 
00034 CUPS_PK_NAME  = 'org.opensuse.CupsPkHelper.Mechanism'
00035 CUPS_PK_PATH  = '/'
00036 CUPS_PK_IFACE = 'org.opensuse.CupsPkHelper.Mechanism'
00037 CUPS_PK_NEED_AUTH = 'org.opensuse.CupsPkHelper.Mechanism.NotPrivileged'
00038 
00039 ######
00040 ###### A polkit-1 based asynchronous CupsPkHelper interface made to
00041 ###### look just like a normal IPPAuthConnection class.  For method
00042 ###### calls that have no equivalent in the CupsPkHelper API, IPP
00043 ###### authentication is used over a CUPS connection in a separate
00044 ###### thread.
00045 ######
00046 
00047 _DevicesGet_uses_new_api = None
00048 
00049 ###
00050 ### A class to handle an asynchronous method call.
00051 ###
00052 class _PK1AsyncMethodCall:
00053     def __init__ (self, bus, conn, pk_method_name, pk_args,
00054                   reply_handler, error_handler, unpack_fn,
00055                   fallback_fn, args, kwds):
00056         self._bus = bus
00057         self._conn = conn
00058         self._pk_method_name = pk_method_name
00059         self._pk_args = pk_args
00060         self._client_reply_handler = reply_handler
00061         self._client_error_handler = error_handler
00062         self._unpack_fn = unpack_fn
00063         self._fallback_fn = fallback_fn
00064         self._fallback_args = args
00065         self._fallback_kwds = kwds
00066         self._destroyed = False
00067         debugprint ("+_PK1AsyncMethodCall: %s" % self)
00068 
00069     def __del__ (self):
00070         debug.debugprint ("-_PK1AsyncMethodCall: %s" % self)
00071 
00072     def call (self):
00073         object = self._bus.get_object(CUPS_PK_NAME, CUPS_PK_PATH)
00074         proxy = dbus.Interface (object, CUPS_PK_IFACE)
00075         pk_method = proxy.get_dbus_method (self._pk_method_name)
00076 
00077         try:
00078             pk_method (*self._pk_args,
00079                         reply_handler=self._pk_reply_handler,
00080                         error_handler=self._pk_error_handler,
00081                         timeout=3600)
00082         except TypeError, e:
00083             debugprint ("Type error in PK call: %s" % e)
00084             self.call_fallback_fn ()
00085 
00086     def _destroy (self):
00087         debugprint ("DESTROY: %s" % self)
00088         self._destroyed = True
00089         del self._bus
00090         del self._conn
00091         del self._pk_method_name
00092         del self._pk_args
00093         del self._client_reply_handler
00094         del self._client_error_handler
00095         del self._unpack_fn
00096         del self._fallback_fn
00097         del self._fallback_args
00098         del self._fallback_kwds
00099 
00100     def _pk_reply_handler (self, error, *args):
00101         if self._destroyed:
00102             return
00103 
00104         if str (error) == '':
00105             gtk.gdk.threads_enter ()
00106             self._client_reply_handler (self._conn, self._unpack_fn (*args))
00107             gtk.gdk.threads_leave ()
00108             self._destroy ()
00109             return
00110 
00111         debugprint ("PolicyKit method failed with: %s" % repr (str (error)))
00112         self.call_fallback_fn ()
00113 
00114     def _pk_error_handler (self, exc):
00115         if self._destroyed:
00116             return
00117 
00118         if exc.get_dbus_name () == CUPS_PK_NEED_AUTH:
00119             exc = cups.IPPError (cups.IPP_NOT_AUTHORIZED, 'pkcancel')
00120             gtk.gdk.threads_enter ()
00121             self._client_error_handler (self._conn, exc)
00122             gtk.gdk.threads_leave ()
00123             self._destroy ()
00124             return
00125 
00126         debugprint ("PolicyKit call to %s did not work: %s" %
00127                     (self._pk_method_name, exc))
00128         self.call_fallback_fn ()
00129 
00130     def call_fallback_fn (self):
00131         # Make the 'connection' parameter consistent with PK callbacks.
00132         self._fallback_kwds["reply_handler"] = self._ipp_reply_handler
00133         self._fallback_kwds["error_handler"] = self._ipp_error_handler
00134         self._fallback_fn (*self._fallback_args, **self._fallback_kwds)
00135 
00136     def _ipp_reply_handler (self, conn, *args):
00137         if self._destroyed:
00138             return
00139 
00140         self._client_reply_handler (self._conn, *args)
00141         self._destroy ()
00142 
00143     def _ipp_error_handler (self, conn, *args):
00144         if self._destroyed:
00145             return
00146 
00147         self._client_error_handler (self._conn, *args)
00148         self._destroy ()
00149 
00150 ###
00151 ### A class for handling FileGet when a temporary file is needed.
00152 ###
00153 class _WriteToTmpFile:
00154     def __init__ (self, kwds, reply_handler, error_handler):
00155         self._reply_handler = reply_handler
00156         self._error_handler = error_handler
00157 
00158         # Create the temporary file in /tmp to ensure that
00159         # cups-pk-helper-mechanism is able to write to it.
00160         (tmpfd, tmpfname) = tempfile.mkstemp (dir="/tmp")
00161         os.close (tmpfd)
00162         self._filename = tmpfname
00163         debugprint ("Created tempfile %s" % tmpfname)
00164         self._kwds = kwds
00165 
00166     def __del__ (self):
00167         try:
00168             os.unlink (self._filename)
00169             debug.debugprint ("Removed tempfile %s" % self._filename)
00170         except:
00171             debug.debugprint ("No tempfile to remove")
00172 
00173     def get_filename (self):
00174         return self._filename
00175 
00176     def reply_handler (self, conn, none):
00177         tmpfd = os.open (self._filename, os.O_RDONLY)
00178         tmpfile = os.fdopen (tmpfd, 'r')
00179         if self._kwds.has_key ("fd"):
00180             fd = self._kwds["fd"]
00181             os.lseek (fd, 0, os.SEEK_SET)
00182             line = tmpfile.readline ()
00183             while line != '':
00184                 os.write (fd, line)
00185                 line = tempfile.readline ()
00186         else:
00187             file_object = self._kwds["file"]
00188             file_object.seek (0)
00189             line = tmpfile.readline ()
00190             while line != '':
00191                 file_object.write (line)
00192                 line = tmpfile.readline ()
00193 
00194         tmpfile.close ()
00195         self._reply_handler (conn, none)
00196 
00197     def error_handler (self, conn, exc):
00198         self._error_handler (conn, exc)
00199 
00200 ###
00201 ### The user-visible class.
00202 ###
00203 class PK1Connection:
00204     def __init__(self, reply_handler=None, error_handler=None,
00205                  host=None, port=None, encryption=None, parent=None):
00206         self._conn = asyncipp.IPPAuthConnection  (reply_handler=reply_handler,
00207                                                   error_handler=error_handler,
00208                                                   host=host, port=port,
00209                                                   encryption=encryption,
00210                                                   parent=parent)
00211 
00212         try:
00213             self._system_bus = dbus.SystemBus()
00214         except (dbus.exceptions.DBusException, AttributeError):
00215             # No system D-Bus.
00216             self._system_bus = None
00217 
00218         global _DevicesGet_uses_new_api
00219         if _DevicesGet_uses_new_api == None and self._system_bus:
00220             try:
00221                 obj = self._system_bus.get_object(CUPS_PK_NAME, CUPS_PK_PATH)
00222                 proxy = dbus.Interface (obj, dbus.INTROSPECTABLE_IFACE)
00223                 api = proxy.Introspect ()
00224                 top = xml.etree.ElementTree.XML (api)
00225                 for interface in top.findall ("interface"):
00226                     if interface.attrib.get ("name") != CUPS_PK_IFACE:
00227                         continue
00228 
00229                     for method in interface.findall ("method"):
00230                         if method.attrib.get ("name") != "DevicesGet":
00231                             continue
00232 
00233                         num_args = 0
00234                         for arg in method.findall ("arg"):
00235                             direction = arg.attrib.get ("direction")
00236                             if direction != "in":
00237                                 continue
00238 
00239                             num_args += 1
00240 
00241                         _DevicesGet_uses_new_api = num_args == 4
00242                         debugprint ("DevicesGet new API: %s" % (num_args == 4))
00243                         break
00244 
00245                     break
00246 
00247             except Exception, e:
00248                 debugprint ("Exception assessing DevicesGet API: %s" % e)
00249 
00250         methodtype = type (self._conn.getPrinters)
00251         bindings = []
00252         for fname in dir (self._conn):
00253             if fname.startswith ('_'):
00254                 continue
00255             fn = getattr (self._conn, fname)
00256             if type (fn) != methodtype:
00257                 continue
00258             if not hasattr (self, fname):
00259                 setattr (self, fname, self._make_binding (fn))
00260                 bindings.append (fname)
00261 
00262         self._bindings = bindings
00263         debugprint ("+%s" % self)
00264 
00265     def __del__ (self):
00266         debug.debugprint ("-%s" % self)
00267 
00268     def _make_binding (self, fn):
00269         def binding (*args, **kwds):
00270             op = _PK1AsyncMethodCall (None, self, None, None,
00271                                       kwds.get ("reply_handler"),
00272                                       kwds.get ("error_handler"),
00273                                       None, fn, args, kwds)
00274             op.call_fallback_fn ()
00275 
00276         return binding
00277 
00278     def destroy (self):
00279         debugprint ("DESTROY: %s" % self)
00280         self._conn.destroy ()
00281 
00282         for binding in self._bindings:
00283             delattr (self, binding)
00284 
00285     def _coerce (self, typ, val):
00286         return typ (val)
00287 
00288     def _args_kwds_to_tuple (self, types, params, args, kwds):
00289         """Collapse args and kwds into a single tuple."""
00290         leftover_kwds = kwds.copy ()
00291         reply_handler = leftover_kwds.get ("reply_handler")
00292         error_handler = leftover_kwds.get ("error_handler")
00293         if leftover_kwds.has_key ("reply_handler"):
00294             del leftover_kwds["reply_handler"]
00295         if leftover_kwds.has_key ("error_handler"):
00296             del leftover_kwds["error_handler"]
00297         if leftover_kwds.has_key ("auth_handler"):
00298             del leftover_kwds["auth_handler"]
00299 
00300         result = [True, reply_handler, error_handler, ()]
00301         if self._system_bus == None:
00302             return result
00303 
00304         tup = []
00305         argindex = 0
00306         for arg in args:
00307             try:
00308                 val = self._coerce (types[argindex], arg)
00309             except IndexError:
00310                 # More args than types.
00311                 kw, default = params[argindex]
00312                 if default != arg:
00313                     return result
00314 
00315                 # It's OK, this is the default value anyway and can be
00316                 # ignored.  Skip to the next one.
00317                 argindex += 1
00318                 continue
00319             except TypeError, e:
00320                 debugprint ("Error converting %s to %s" %
00321                             (repr (arg), types[argindex]))
00322                 return result
00323 
00324             tup.append (val)
00325             argindex += 1
00326 
00327         for kw, default in params[argindex:]:
00328             if leftover_kwds.has_key (kw):
00329                 try:
00330                     val = self._coerce (types[argindex], leftover_kwds[kw])
00331                 except TypeError, e:
00332                     debugprint ("Error converting %s to %s" %
00333                                 (repr (leftover_kwds[kw]), types[argindex]))
00334                     return result
00335 
00336                 tup.append (val)
00337                 del leftover_kwds[kw]
00338             else:
00339                 tup.append (default)
00340 
00341             argindex += 1
00342 
00343         if leftover_kwds:
00344             debugprint ("Leftover keywords: %s" % repr (leftover_kwds.keys ()))
00345             return result
00346 
00347         result[0] = False
00348         result[3] = tuple (tup)
00349         debugprint ("Converted %s/%s to %s" % (args, kwds, tuple (tup)))
00350         return result
00351 
00352     def _call_with_pk (self, use_pycups, pk_method_name, pk_args,
00353                        reply_handler, error_handler, unpack_fn,
00354                        fallback_fn, args, kwds):
00355         asyncmethodcall = _PK1AsyncMethodCall (self._system_bus, self,
00356                                                pk_method_name, pk_args,
00357                                                reply_handler,
00358                                                error_handler,
00359                                                unpack_fn, fallback_fn,
00360                                                args, kwds)
00361 
00362         if not use_pycups:
00363             try:
00364                 debugprint ("Calling PK method %s" % pk_method_name)
00365                 asyncmethodcall.call ()
00366             except dbus.DBusException, e:
00367                 debugprint ("D-Bus call failed: %s" % repr (e))
00368                 use_pycups = True
00369 
00370         if use_pycups:
00371             return asyncmethodcall.call_fallback_fn ()
00372 
00373     def _nothing_to_unpack (self):
00374         return None
00375 
00376     def getDevices (self, *args, **kwds):
00377         global _DevicesGet_uses_new_api
00378         if _DevicesGet_uses_new_api:
00379             (use_pycups, reply_handler, error_handler,
00380              tup) = self._args_kwds_to_tuple ([int, int, list, list],
00381                                               [("timeout", 0),
00382                                                ("limit", 0),
00383                                                ("include_schemes", []),
00384                                                ("exclude_schemes", [])],
00385                                               args, kwds)
00386         else:
00387             (use_pycups, reply_handler, error_handler,
00388              tup) = self._args_kwds_to_tuple ([int, list, list],
00389                                               [("limit", 0),
00390                                                ("include_schemes", []),
00391                                                ("exclude_schemes", [])],
00392                                               args, kwds)
00393 
00394             if not use_pycups:
00395                 # Special handling for include_schemes/exclude_schemes.
00396                 # Convert from list to ","-separated string.
00397                 newtup = list (tup)
00398                 for paramindex in [1, 2]:
00399                     if len (newtup[paramindex]) > 0:
00400                         newtup[paramindex] = reduce (lambda x, y:
00401                                                          x + "," + y,
00402                                                      newtup[paramindex])
00403                     else:
00404                         newtup[paramindex] = ""
00405 
00406                 tup = tuple (newtup)
00407 
00408         self._call_with_pk (use_pycups,
00409                             'DevicesGet', tup, reply_handler, error_handler,
00410                             self._unpack_getDevices_reply,
00411                             self._conn.getDevices, args, kwds)
00412 
00413     def _unpack_getDevices_reply (self, dbusdict):
00414         result_str = dict()
00415         for key, value in dbusdict.iteritems ():
00416             if type (key) == dbus.String:
00417                 result_str[str (key)] = str (value)
00418             else:
00419                 result_str[key] = value
00420 
00421         # cups-pk-helper returns all devices in one dictionary.
00422         # Keys of different devices are distinguished by ':n' postfix.
00423 
00424         devices = dict()
00425         n = 0
00426         affix = ':' + str (n)
00427         device_keys = [x for x in result_str.keys () if x.endswith (affix)]
00428         while len (device_keys) > 0:
00429             device_uri = None
00430             device_dict = dict()
00431             for keywithaffix in device_keys:
00432                 key = keywithaffix[:len (keywithaffix) - len (affix)]
00433                 if key != 'device-uri':
00434                     device_dict[key] = result_str[keywithaffix]
00435                 else:
00436                     device_uri = result_str[keywithaffix]
00437 
00438             if device_uri != None:
00439                 devices[device_uri] = device_dict
00440 
00441             n += 1
00442             affix = ':' + str (n)
00443             device_keys = [x for x in result_str.keys () if x.endswith (affix)]
00444 
00445         return devices
00446 
00447     def cancelJob (self, *args, **kwds):
00448         (use_pycups, reply_handler, error_handler,
00449          tup) = self._args_kwds_to_tuple ([int, bool],
00450                                           [(None, None),
00451                                            (None, False)], # purge_job
00452                                           args, kwds)
00453 
00454         self._call_with_pk (use_pycups,
00455                             'JobCancelPurge', tup, reply_handler, error_handler,
00456                             self._nothing_to_unpack,
00457                             self._conn.cancelJob, args, kwds)
00458 
00459     def setJobHoldUntil (self, *args, **kwds):
00460         (use_pycups, reply_handler, error_handler,
00461          tup) = self._args_kwds_to_tuple ([int, str],
00462                                           [(None, None),
00463                                            (None, None)],
00464                                           args, kwds)
00465 
00466         self._call_with_pk (use_pycups,
00467                             'JobSetHoldUntil', tup, reply_handler,
00468                             error_handler, self._nothing_to_unpack,
00469                             self._conn.setJobHoldUntil, args, kwds)
00470 
00471     def restartJob (self, *args, **kwds):
00472         (use_pycups, reply_handler, error_handler,
00473          tup) = self._args_kwds_to_tuple ([int],
00474                                           [(None, None)],
00475                                           args, kwds)
00476 
00477         self._call_with_pk (use_pycups,
00478                             'JobRestart', tup, reply_handler,
00479                             error_handler, self._nothing_to_unpack,
00480                             self._conn.restartJob, args, kwds)
00481 
00482     def getFile (self, *args, **kwds):
00483         (use_pycups, reply_handler, error_handler,
00484          tup) = self._args_kwds_to_tuple ([str, str],
00485                                           [("resource", None),
00486                                            ("filename", None)],
00487                                           args, kwds)
00488 
00489         # getFile(resource, filename=None, fd=-1, file=None) -> None
00490         if use_pycups:
00491             if ((len (args) == 0 and kwds.has_key ('resource')) or
00492                 (len (args) == 1)):
00493                 can_use_tempfile = True
00494                 for each in kwds.keys ():
00495                     if each not in ['resource', 'fd', 'file',
00496                                     'reply_handler', 'error_handler']:
00497                         can_use_tempfile = False
00498                         break
00499 
00500                 if can_use_tempfile:
00501                     # We can still use PackageKit for this.
00502                     if len (args) == 0:
00503                         resource = kwds["resource"]
00504                     else:
00505                         resource = args[0]
00506 
00507                     wrapper = _WriteToTmpFile (kwds,
00508                                                reply_handler,
00509                                                error_handler)
00510                     self._call_with_pk (False,
00511                                         'FileGet',
00512                                         (resource, wrapper.get_filename ()),
00513                                         wrapper.reply_handler,
00514                                         wrapper.error_handler,
00515                                         self._nothing_to_unpack,
00516                                         self._conn.getFile, args, kwds)
00517                     return
00518 
00519         self._call_with_pk (use_pycups,
00520                             'FileGet', tup, reply_handler,
00521                             error_handler, self._nothing_to_unpack,
00522                             self._conn.getFile, args, kwds)
00523 
00524     ## etc
00525     ## Still to implement:
00526     ## putFile
00527     ## addPrinter
00528     ## setPrinterDevice
00529     ## setPrinterInfo
00530     ## setPrinterLocation
00531     ## setPrinterShared
00532     ## setPrinterJobSheets
00533     ## setPrinterErrorPolicy
00534     ## setPrinterOpPolicy
00535     ## setPrinterUsersAllowed
00536     ## setPrinterUsersDenied
00537     ## addPrinterOptionDefault
00538     ## deletePrinterOptionDefault
00539     ## deletePrinter
00540     ## addPrinterToClass
00541     ## deletePrinterFromClass
00542     ## deleteClass
00543     ## setDefault
00544     ## enablePrinter
00545     ## disablePrinter
00546     ## acceptJobs
00547     ## rejectJobs
00548     ## adminGetServerSettings
00549     ## adminSetServerSettings
00550     ## ...
00551 
00552 if __name__ == '__main__':
00553     import gtk
00554     gobject.threads_init ()
00555     from debug import set_debugging
00556     set_debugging (True)
00557     class UI:
00558         def __init__ (self):
00559             w = gtk.Window ()
00560             v = gtk.VBox ()
00561             w.add (v)
00562             b = gtk.Button ("Go")
00563             v.pack_start (b)
00564             b.connect ("clicked", self.button_clicked)
00565             b = gtk.Button ("Fetch")
00566             v.pack_start (b)
00567             b.connect ("clicked", self.fetch_clicked)
00568             b.set_sensitive (False)
00569             self.fetch_button = b
00570             b = gtk.Button ("Cancel job")
00571             v.pack_start (b)
00572             b.connect ("clicked", self.cancel_clicked)
00573             b.set_sensitive (False)
00574             self.cancel_button = b
00575             b = gtk.Button ("Get file")
00576             v.pack_start (b)
00577             b.connect ("clicked", self.get_file_clicked)
00578             b.set_sensitive (False)
00579             self.get_file_button = b
00580             b = gtk.Button ("Something harmless")
00581             v.pack_start (b)
00582             b.connect ("clicked", self.harmless_clicked)
00583             b.set_sensitive (False)
00584             self.harmless_button = b
00585             w.connect ("destroy", self.destroy)
00586             w.show_all ()
00587             self.conn = None
00588             debugprint ("+%s" % self)
00589 
00590         def __del__ (self):
00591             debug.debugprint ("-%s" % self)
00592 
00593         def destroy (self, window):
00594             debugprint ("DESTROY: %s" % self)
00595             try:
00596                 self.conn.destroy ()
00597                 del self.conn
00598             except AttributeError:
00599                 pass
00600 
00601             gtk.main_quit ()
00602 
00603         def button_clicked (self, button):
00604             if self.conn:
00605                 self.conn.destroy ()
00606 
00607             self.conn = PK1Connection (reply_handler=self.connected,
00608                                        error_handler=self.connection_error)
00609 
00610         def connected (self, conn, result):
00611             print "Connected"
00612             self.fetch_button.set_sensitive (True)
00613             self.cancel_button.set_sensitive (True)
00614             self.get_file_button.set_sensitive (True)
00615             self.harmless_button.set_sensitive (True)
00616 
00617         def connection_error (self, conn, error):
00618             print "Failed to connect"
00619             raise error
00620 
00621         def fetch_clicked (self, button):
00622             print ("fetch devices...")
00623             self.conn.getDevices (reply_handler=self.got_devices,
00624                                   error_handler=self.get_devices_error)
00625 
00626         def got_devices (self, conn, devices):
00627             if conn != self.conn:
00628                 print "Ignoring stale reply"
00629                 return
00630 
00631             print "got devices: %s" % devices
00632 
00633         def get_devices_error (self, conn, exc):
00634             if conn != self.conn:
00635                 print "Ignoring stale error"
00636                 return
00637 
00638             print "devices error: %s" % repr (exc)
00639 
00640         def cancel_clicked (self, button):
00641             print "Cancel job..."
00642             self.conn.cancelJob (1,
00643                                  reply_handler=self.job_canceled,
00644                                  error_handler=self.cancel_job_error)
00645 
00646         def job_canceled (self, conn, none):
00647             if conn != self.conn:
00648                 print "Ignoring stale reply for %s" % conn
00649                 return
00650 
00651             print "Job canceled"
00652 
00653         def cancel_job_error (self, conn, exc):
00654             if conn != self.conn:
00655                 print "Ignoring stale error for %s" % conn
00656                 return
00657 
00658             print "cancel error: %s" % repr (exc)
00659 
00660         def get_file_clicked (self, button):
00661             self.my_file = file ("cupsd.conf", "w")
00662             self.conn.getFile ("/admin/conf/cupsd.conf", file=self.my_file,
00663                                reply_handler=self.got_file,
00664                                error_handler=self.get_file_error)
00665 
00666         def got_file (self, conn, none):
00667             if conn != self.conn:
00668                 print "Ignoring stale reply for %s" % conn
00669                 return
00670 
00671             print "Got file"
00672 
00673         def get_file_error (self, conn, exc):
00674             if conn != self.conn:
00675                 print "Ignoring stale error"
00676                 return
00677 
00678             print "get file error: %s" % repr (exc)
00679 
00680         def harmless_clicked (self, button):
00681             self.conn.getJobs (reply_handler=self.got_jobs,
00682                                error_handler=self.get_jobs_error)
00683 
00684         def got_jobs (self, conn, result):
00685             if conn != self.conn:
00686                 print "Ignoring stale reply from %s" % repr (conn)
00687                 return
00688             print result
00689 
00690         def get_jobs_error (self, exc):
00691             print "get jobs error: %s" % repr (exc)
00692 
00693     UI ()
00694     from dbus.mainloop.glib import DBusGMainLoop
00695     DBusGMainLoop (set_as_default=True)
00696     gtk.main ()