home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / system-config-printer / asyncpk1.py < prev    next >
Encoding:
Python Source  |  2010-09-28  |  24.6 KB  |  698 lines

  1. #!/usr/bin/env python
  2.  
  3. ## Copyright (C) 2007, 2008, 2009, 2010 Red Hat, Inc.
  4. ## Copyright (C) 2008 Novell, Inc.
  5. ## Authors: Tim Waugh <twaugh@redhat.com>, Vincent Untz
  6.  
  7. ## This program is free software; you can redistribute it and/or modify
  8. ## it under the terms of the GNU General Public License as published by
  9. ## the Free Software Foundation; either version 2 of the License, or
  10. ## (at your option) any later version.
  11.  
  12. ## This program is distributed in the hope that it will be useful,
  13. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. ## GNU General Public License for more details.
  16.  
  17. ## You should have received a copy of the GNU General Public License
  18. ## along with this program; if not, write to the Free Software
  19. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21. import cups
  22. import dbus
  23. import gobject
  24. import gtk
  25. import os
  26. import sys
  27. import tempfile
  28. import xml.etree.ElementTree
  29.  
  30. import asyncipp
  31. from debug import *
  32. import debug
  33.  
  34. from dbus.mainloop.glib import DBusGMainLoop
  35. DBusGMainLoop (set_as_default=True)
  36.  
  37. CUPS_PK_NAME  = 'org.opensuse.CupsPkHelper.Mechanism'
  38. CUPS_PK_PATH  = '/'
  39. CUPS_PK_IFACE = 'org.opensuse.CupsPkHelper.Mechanism'
  40. CUPS_PK_NEED_AUTH = 'org.opensuse.CupsPkHelper.Mechanism.NotPrivileged'
  41.  
  42. ######
  43. ###### A polkit-1 based asynchronous CupsPkHelper interface made to
  44. ###### look just like a normal IPPAuthConnection class.  For method
  45. ###### calls that have no equivalent in the CupsPkHelper API, IPP
  46. ###### authentication is used over a CUPS connection in a separate
  47. ###### thread.
  48. ######
  49.  
  50. _DevicesGet_uses_new_api = None
  51.  
  52. ###
  53. ### A class to handle an asynchronous method call.
  54. ###
  55. class _PK1AsyncMethodCall:
  56.     def __init__ (self, bus, conn, pk_method_name, pk_args,
  57.                   reply_handler, error_handler, unpack_fn,
  58.                   fallback_fn, args, kwds):
  59.         self._bus = bus
  60.         self._conn = conn
  61.         self._pk_method_name = pk_method_name
  62.         self._pk_args = pk_args
  63.         self._client_reply_handler = reply_handler
  64.         self._client_error_handler = error_handler
  65.         self._unpack_fn = unpack_fn
  66.         self._fallback_fn = fallback_fn
  67.         self._fallback_args = args
  68.         self._fallback_kwds = kwds
  69.         self._destroyed = False
  70.         debugprint ("+_PK1AsyncMethodCall: %s" % self)
  71.  
  72.     def __del__ (self):
  73.         debug.debugprint ("-_PK1AsyncMethodCall: %s" % self)
  74.  
  75.     def call (self):
  76.         object = self._bus.get_object(CUPS_PK_NAME, CUPS_PK_PATH)
  77.         proxy = dbus.Interface (object, CUPS_PK_IFACE)
  78.         pk_method = proxy.get_dbus_method (self._pk_method_name)
  79.  
  80.         try:
  81.             pk_method (*self._pk_args,
  82.                         reply_handler=self._pk_reply_handler,
  83.                         error_handler=self._pk_error_handler,
  84.                         timeout=3600)
  85.         except TypeError, e:
  86.             debugprint ("Type error in PK call: %s" % e)
  87.             self.call_fallback_fn ()
  88.  
  89.     def _destroy (self):
  90.         debugprint ("DESTROY: %s" % self)
  91.         self._destroyed = True
  92.         del self._bus
  93.         del self._conn
  94.         del self._pk_method_name
  95.         del self._pk_args
  96.         del self._client_reply_handler
  97.         del self._client_error_handler
  98.         del self._unpack_fn
  99.         del self._fallback_fn
  100.         del self._fallback_args
  101.         del self._fallback_kwds
  102.  
  103.     def _pk_reply_handler (self, error, *args):
  104.         if self._destroyed:
  105.             return
  106.  
  107.         if str (error) == '':
  108.             gtk.gdk.threads_enter ()
  109.             self._client_reply_handler (self._conn, self._unpack_fn (*args))
  110.             gtk.gdk.threads_leave ()
  111.             self._destroy ()
  112.             return
  113.  
  114.         debugprint ("PolicyKit method failed with: %s" % repr (str (error)))
  115.         self.call_fallback_fn ()
  116.  
  117.     def _pk_error_handler (self, exc):
  118.         if self._destroyed:
  119.             return
  120.  
  121.         if exc.get_dbus_name () == CUPS_PK_NEED_AUTH:
  122.             exc = cups.IPPError (cups.IPP_NOT_AUTHORIZED, 'pkcancel')
  123.             gtk.gdk.threads_enter ()
  124.             self._client_error_handler (self._conn, exc)
  125.             gtk.gdk.threads_leave ()
  126.             self._destroy ()
  127.             return
  128.  
  129.         debugprint ("PolicyKit call to %s did not work: %s" %
  130.                     (self._pk_method_name, exc))
  131.         self.call_fallback_fn ()
  132.  
  133.     def call_fallback_fn (self):
  134.         # Make the 'connection' parameter consistent with PK callbacks.
  135.         self._fallback_kwds["reply_handler"] = self._ipp_reply_handler
  136.         self._fallback_kwds["error_handler"] = self._ipp_error_handler
  137.         self._fallback_fn (*self._fallback_args, **self._fallback_kwds)
  138.  
  139.     def _ipp_reply_handler (self, conn, *args):
  140.         if self._destroyed:
  141.             return
  142.  
  143.         self._client_reply_handler (self._conn, *args)
  144.         self._destroy ()
  145.  
  146.     def _ipp_error_handler (self, conn, *args):
  147.         if self._destroyed:
  148.             return
  149.  
  150.         self._client_error_handler (self._conn, *args)
  151.         self._destroy ()
  152.  
  153. ###
  154. ### A class for handling FileGet when a temporary file is needed.
  155. ###
  156. class _WriteToTmpFile:
  157.     def __init__ (self, kwds, reply_handler, error_handler):
  158.         self._reply_handler = reply_handler
  159.         self._error_handler = error_handler
  160.  
  161.         # Create the temporary file in /tmp to ensure that
  162.         # cups-pk-helper-mechanism is able to write to it.
  163.         (tmpfd, tmpfname) = tempfile.mkstemp (dir="/tmp")
  164.         os.close (tmpfd)
  165.         self._filename = tmpfname
  166.         debugprint ("Created tempfile %s" % tmpfname)
  167.         self._kwds = kwds
  168.  
  169.     def __del__ (self):
  170.         try:
  171.             os.unlink (self._filename)
  172.             debug.debugprint ("Removed tempfile %s" % self._filename)
  173.         except:
  174.             debug.debugprint ("No tempfile to remove")
  175.  
  176.     def get_filename (self):
  177.         return self._filename
  178.  
  179.     def reply_handler (self, conn, none):
  180.         tmpfd = os.open (self._filename, os.O_RDONLY)
  181.         tmpfile = os.fdopen (tmpfd, 'r')
  182.         if self._kwds.has_key ("fd"):
  183.             fd = self._kwds["fd"]
  184.             os.lseek (fd, 0, os.SEEK_SET)
  185.             line = tmpfile.readline ()
  186.             while line != '':
  187.                 os.write (fd, line)
  188.                 line = tempfile.readline ()
  189.         else:
  190.             file_object = self._kwds["file"]
  191.             file_object.seek (0)
  192.             line = tmpfile.readline ()
  193.             while line != '':
  194.                 file_object.write (line)
  195.                 line = tmpfile.readline ()
  196.  
  197.         tmpfile.close ()
  198.         self._reply_handler (conn, none)
  199.  
  200.     def error_handler (self, conn, exc):
  201.         self._error_handler (conn, exc)
  202.  
  203. ###
  204. ### The user-visible class.
  205. ###
  206. class PK1Connection:
  207.     def __init__(self, reply_handler=None, error_handler=None,
  208.                  host=None, port=None, encryption=None, parent=None):
  209.         self._conn = asyncipp.IPPAuthConnection  (reply_handler=reply_handler,
  210.                                                   error_handler=error_handler,
  211.                                                   host=host, port=port,
  212.                                                   encryption=encryption,
  213.                                                   parent=parent)
  214.  
  215.         try:
  216.             self._system_bus = dbus.SystemBus()
  217.         except (dbus.exceptions.DBusException, AttributeError):
  218.             # No system D-Bus.
  219.             self._system_bus = None
  220.  
  221.         global _DevicesGet_uses_new_api
  222.         if _DevicesGet_uses_new_api == None and self._system_bus:
  223.             try:
  224.                 obj = self._system_bus.get_object(CUPS_PK_NAME, CUPS_PK_PATH)
  225.                 proxy = dbus.Interface (obj, dbus.INTROSPECTABLE_IFACE)
  226.                 api = proxy.Introspect ()
  227.                 top = xml.etree.ElementTree.XML (api)
  228.                 for interface in top.findall ("interface"):
  229.                     if interface.attrib.get ("name") != CUPS_PK_IFACE:
  230.                         continue
  231.  
  232.                     for method in interface.findall ("method"):
  233.                         if method.attrib.get ("name") != "DevicesGet":
  234.                             continue
  235.  
  236.                         num_args = 0
  237.                         for arg in method.findall ("arg"):
  238.                             direction = arg.attrib.get ("direction")
  239.                             if direction != "in":
  240.                                 continue
  241.  
  242.                             num_args += 1
  243.  
  244.                         _DevicesGet_uses_new_api = num_args == 4
  245.                         debugprint ("DevicesGet new API: %s" % (num_args == 4))
  246.                         break
  247.  
  248.                     break
  249.  
  250.             except Exception, e:
  251.                 debugprint ("Exception assessing DevicesGet API: %s" % e)
  252.  
  253.         methodtype = type (self._conn.getPrinters)
  254.         bindings = []
  255.         for fname in dir (self._conn):
  256.             if fname.startswith ('_'):
  257.                 continue
  258.             fn = getattr (self._conn, fname)
  259.             if type (fn) != methodtype:
  260.                 continue
  261.             if not hasattr (self, fname):
  262.                 setattr (self, fname, self._make_binding (fn))
  263.                 bindings.append (fname)
  264.  
  265.         self._bindings = bindings
  266.         debugprint ("+%s" % self)
  267.  
  268.     def __del__ (self):
  269.         debug.debugprint ("-%s" % self)
  270.  
  271.     def _make_binding (self, fn):
  272.         def binding (*args, **kwds):
  273.             op = _PK1AsyncMethodCall (None, self, None, None,
  274.                                       kwds.get ("reply_handler"),
  275.                                       kwds.get ("error_handler"),
  276.                                       None, fn, args, kwds)
  277.             op.call_fallback_fn ()
  278.  
  279.         return binding
  280.  
  281.     def destroy (self):
  282.         debugprint ("DESTROY: %s" % self)
  283.         self._conn.destroy ()
  284.  
  285.         for binding in self._bindings:
  286.             delattr (self, binding)
  287.  
  288.     def _coerce (self, typ, val):
  289.         return typ (val)
  290.  
  291.     def _args_kwds_to_tuple (self, types, params, args, kwds):
  292.         """Collapse args and kwds into a single tuple."""
  293.         leftover_kwds = kwds.copy ()
  294.         reply_handler = leftover_kwds.get ("reply_handler")
  295.         error_handler = leftover_kwds.get ("error_handler")
  296.         if leftover_kwds.has_key ("reply_handler"):
  297.             del leftover_kwds["reply_handler"]
  298.         if leftover_kwds.has_key ("error_handler"):
  299.             del leftover_kwds["error_handler"]
  300.         if leftover_kwds.has_key ("auth_handler"):
  301.             del leftover_kwds["auth_handler"]
  302.  
  303.         result = [True, reply_handler, error_handler, ()]
  304.         if self._system_bus == None:
  305.             return result
  306.  
  307.         tup = []
  308.         argindex = 0
  309.         for arg in args:
  310.             try:
  311.                 val = self._coerce (types[argindex], arg)
  312.             except IndexError:
  313.                 # More args than types.
  314.                 kw, default = params[argindex]
  315.                 if default != arg:
  316.                     return result
  317.  
  318.                 # It's OK, this is the default value anyway and can be
  319.                 # ignored.  Skip to the next one.
  320.                 argindex += 1
  321.                 continue
  322.             except TypeError, e:
  323.                 debugprint ("Error converting %s to %s" %
  324.                             (repr (arg), types[argindex]))
  325.                 return result
  326.  
  327.             tup.append (val)
  328.             argindex += 1
  329.  
  330.         for kw, default in params[argindex:]:
  331.             if leftover_kwds.has_key (kw):
  332.                 try:
  333.                     val = self._coerce (types[argindex], leftover_kwds[kw])
  334.                 except TypeError, e:
  335.                     debugprint ("Error converting %s to %s" %
  336.                                 (repr (leftover_kwds[kw]), types[argindex]))
  337.                     return result
  338.  
  339.                 tup.append (val)
  340.                 del leftover_kwds[kw]
  341.             else:
  342.                 tup.append (default)
  343.  
  344.             argindex += 1
  345.  
  346.         if leftover_kwds:
  347.             debugprint ("Leftover keywords: %s" % repr (leftover_kwds.keys ()))
  348.             return result
  349.  
  350.         result[0] = False
  351.         result[3] = tuple (tup)
  352.         debugprint ("Converted %s/%s to %s" % (args, kwds, tuple (tup)))
  353.         return result
  354.  
  355.     def _call_with_pk (self, use_pycups, pk_method_name, pk_args,
  356.                        reply_handler, error_handler, unpack_fn,
  357.                        fallback_fn, args, kwds):
  358.         asyncmethodcall = _PK1AsyncMethodCall (self._system_bus, self,
  359.                                                pk_method_name, pk_args,
  360.                                                reply_handler,
  361.                                                error_handler,
  362.                                                unpack_fn, fallback_fn,
  363.                                                args, kwds)
  364.  
  365.         if not use_pycups:
  366.             try:
  367.                 debugprint ("Calling PK method %s" % pk_method_name)
  368.                 asyncmethodcall.call ()
  369.             except dbus.DBusException, e:
  370.                 debugprint ("D-Bus call failed: %s" % repr (e))
  371.                 use_pycups = True
  372.  
  373.         if use_pycups:
  374.             return asyncmethodcall.call_fallback_fn ()
  375.  
  376.     def _nothing_to_unpack (self):
  377.         return None
  378.  
  379.     def getDevices (self, *args, **kwds):
  380.         global _DevicesGet_uses_new_api
  381.         if _DevicesGet_uses_new_api:
  382.             (use_pycups, reply_handler, error_handler,
  383.              tup) = self._args_kwds_to_tuple ([int, int, list, list],
  384.                                               [("timeout", 0),
  385.                                                ("limit", 0),
  386.                                                ("include_schemes", []),
  387.                                                ("exclude_schemes", [])],
  388.                                               args, kwds)
  389.         else:
  390.             (use_pycups, reply_handler, error_handler,
  391.              tup) = self._args_kwds_to_tuple ([int, list, list],
  392.                                               [("limit", 0),
  393.                                                ("include_schemes", []),
  394.                                                ("exclude_schemes", [])],
  395.                                               args, kwds)
  396.  
  397.             if not use_pycups:
  398.                 # Special handling for include_schemes/exclude_schemes.
  399.                 # Convert from list to ","-separated string.
  400.                 newtup = list (tup)
  401.                 for paramindex in [1, 2]:
  402.                     if len (newtup[paramindex]) > 0:
  403.                         newtup[paramindex] = reduce (lambda x, y:
  404.                                                          x + "," + y,
  405.                                                      newtup[paramindex])
  406.                     else:
  407.                         newtup[paramindex] = ""
  408.  
  409.                 tup = tuple (newtup)
  410.  
  411.         self._call_with_pk (use_pycups,
  412.                             'DevicesGet', tup, reply_handler, error_handler,
  413.                             self._unpack_getDevices_reply,
  414.                             self._conn.getDevices, args, kwds)
  415.  
  416.     def _unpack_getDevices_reply (self, dbusdict):
  417.         result_str = dict()
  418.         for key, value in dbusdict.iteritems ():
  419.             if type (key) == dbus.String:
  420.                 result_str[str (key)] = str (value)
  421.             else:
  422.                 result_str[key] = value
  423.  
  424.         # cups-pk-helper returns all devices in one dictionary.
  425.         # Keys of different devices are distinguished by ':n' postfix.
  426.  
  427.         devices = dict()
  428.         n = 0
  429.         affix = ':' + str (n)
  430.         device_keys = [x for x in result_str.keys () if x.endswith (affix)]
  431.         while len (device_keys) > 0:
  432.             device_uri = None
  433.             device_dict = dict()
  434.             for keywithaffix in device_keys:
  435.                 key = keywithaffix[:len (keywithaffix) - len (affix)]
  436.                 if key != 'device-uri':
  437.                     device_dict[key] = result_str[keywithaffix]
  438.                 else:
  439.                     device_uri = result_str[keywithaffix]
  440.  
  441.             if device_uri != None:
  442.                 devices[device_uri] = device_dict
  443.  
  444.             n += 1
  445.             affix = ':' + str (n)
  446.             device_keys = [x for x in result_str.keys () if x.endswith (affix)]
  447.  
  448.         return devices
  449.  
  450.     def cancelJob (self, *args, **kwds):
  451.         (use_pycups, reply_handler, error_handler,
  452.          tup) = self._args_kwds_to_tuple ([int, bool],
  453.                                           [(None, None),
  454.                                            (None, False)], # purge_job
  455.                                           args, kwds)
  456.  
  457.         self._call_with_pk (use_pycups,
  458.                             'JobCancelPurge', tup, reply_handler, error_handler,
  459.                             self._nothing_to_unpack,
  460.                             self._conn.cancelJob, args, kwds)
  461.  
  462.     def setJobHoldUntil (self, *args, **kwds):
  463.         (use_pycups, reply_handler, error_handler,
  464.          tup) = self._args_kwds_to_tuple ([int, str],
  465.                                           [(None, None),
  466.                                            (None, None)],
  467.                                           args, kwds)
  468.  
  469.         self._call_with_pk (use_pycups,
  470.                             'JobSetHoldUntil', tup, reply_handler,
  471.                             error_handler, self._nothing_to_unpack,
  472.                             self._conn.setJobHoldUntil, args, kwds)
  473.  
  474.     def restartJob (self, *args, **kwds):
  475.         (use_pycups, reply_handler, error_handler,
  476.          tup) = self._args_kwds_to_tuple ([int],
  477.                                           [(None, None)],
  478.                                           args, kwds)
  479.  
  480.         self._call_with_pk (use_pycups,
  481.                             'JobRestart', tup, reply_handler,
  482.                             error_handler, self._nothing_to_unpack,
  483.                             self._conn.restartJob, args, kwds)
  484.  
  485.     def getFile (self, *args, **kwds):
  486.         (use_pycups, reply_handler, error_handler,
  487.          tup) = self._args_kwds_to_tuple ([str, str],
  488.                                           [("resource", None),
  489.                                            ("filename", None)],
  490.                                           args, kwds)
  491.  
  492.         # getFile(resource, filename=None, fd=-1, file=None) -> None
  493.         if use_pycups:
  494.             if ((len (args) == 0 and kwds.has_key ('resource')) or
  495.                 (len (args) == 1)):
  496.                 can_use_tempfile = True
  497.                 for each in kwds.keys ():
  498.                     if each not in ['resource', 'fd', 'file',
  499.                                     'reply_handler', 'error_handler']:
  500.                         can_use_tempfile = False
  501.                         break
  502.  
  503.                 if can_use_tempfile:
  504.                     # We can still use PackageKit for this.
  505.                     if len (args) == 0:
  506.                         resource = kwds["resource"]
  507.                     else:
  508.                         resource = args[0]
  509.  
  510.                     wrapper = _WriteToTmpFile (kwds,
  511.                                                reply_handler,
  512.                                                error_handler)
  513.                     self._call_with_pk (False,
  514.                                         'FileGet',
  515.                                         (resource, wrapper.get_filename ()),
  516.                                         wrapper.reply_handler,
  517.                                         wrapper.error_handler,
  518.                                         self._nothing_to_unpack,
  519.                                         self._conn.getFile, args, kwds)
  520.                     return
  521.  
  522.         self._call_with_pk (use_pycups,
  523.                             'FileGet', tup, reply_handler,
  524.                             error_handler, self._nothing_to_unpack,
  525.                             self._conn.getFile, args, kwds)
  526.  
  527.     ## etc
  528.     ## Still to implement:
  529.     ## putFile
  530.     ## addPrinter
  531.     ## setPrinterDevice
  532.     ## setPrinterInfo
  533.     ## setPrinterLocation
  534.     ## setPrinterShared
  535.     ## setPrinterJobSheets
  536.     ## setPrinterErrorPolicy
  537.     ## setPrinterOpPolicy
  538.     ## setPrinterUsersAllowed
  539.     ## setPrinterUsersDenied
  540.     ## addPrinterOptionDefault
  541.     ## deletePrinterOptionDefault
  542.     ## deletePrinter
  543.     ## addPrinterToClass
  544.     ## deletePrinterFromClass
  545.     ## deleteClass
  546.     ## setDefault
  547.     ## enablePrinter
  548.     ## disablePrinter
  549.     ## acceptJobs
  550.     ## rejectJobs
  551.     ## adminGetServerSettings
  552.     ## adminSetServerSettings
  553.     ## ...
  554.  
  555. if __name__ == '__main__':
  556.     import gtk
  557.     gobject.threads_init ()
  558.     from debug import set_debugging
  559.     set_debugging (True)
  560.     class UI:
  561.         def __init__ (self):
  562.             w = gtk.Window ()
  563.             v = gtk.VBox ()
  564.             w.add (v)
  565.             b = gtk.Button ("Go")
  566.             v.pack_start (b)
  567.             b.connect ("clicked", self.button_clicked)
  568.             b = gtk.Button ("Fetch")
  569.             v.pack_start (b)
  570.             b.connect ("clicked", self.fetch_clicked)
  571.             b.set_sensitive (False)
  572.             self.fetch_button = b
  573.             b = gtk.Button ("Cancel job")
  574.             v.pack_start (b)
  575.             b.connect ("clicked", self.cancel_clicked)
  576.             b.set_sensitive (False)
  577.             self.cancel_button = b
  578.             b = gtk.Button ("Get file")
  579.             v.pack_start (b)
  580.             b.connect ("clicked", self.get_file_clicked)
  581.             b.set_sensitive (False)
  582.             self.get_file_button = b
  583.             b = gtk.Button ("Something harmless")
  584.             v.pack_start (b)
  585.             b.connect ("clicked", self.harmless_clicked)
  586.             b.set_sensitive (False)
  587.             self.harmless_button = b
  588.             w.connect ("destroy", self.destroy)
  589.             w.show_all ()
  590.             self.conn = None
  591.             debugprint ("+%s" % self)
  592.  
  593.         def __del__ (self):
  594.             debug.debugprint ("-%s" % self)
  595.  
  596.         def destroy (self, window):
  597.             debugprint ("DESTROY: %s" % self)
  598.             try:
  599.                 self.conn.destroy ()
  600.                 del self.conn
  601.             except AttributeError:
  602.                 pass
  603.  
  604.             gtk.main_quit ()
  605.  
  606.         def button_clicked (self, button):
  607.             if self.conn:
  608.                 self.conn.destroy ()
  609.  
  610.             self.conn = PK1Connection (reply_handler=self.connected,
  611.                                        error_handler=self.connection_error)
  612.  
  613.         def connected (self, conn, result):
  614.             print "Connected"
  615.             self.fetch_button.set_sensitive (True)
  616.             self.cancel_button.set_sensitive (True)
  617.             self.get_file_button.set_sensitive (True)
  618.             self.harmless_button.set_sensitive (True)
  619.  
  620.         def connection_error (self, conn, error):
  621.             print "Failed to connect"
  622.             raise error
  623.  
  624.         def fetch_clicked (self, button):
  625.             print ("fetch devices...")
  626.             self.conn.getDevices (reply_handler=self.got_devices,
  627.                                   error_handler=self.get_devices_error)
  628.  
  629.         def got_devices (self, conn, devices):
  630.             if conn != self.conn:
  631.                 print "Ignoring stale reply"
  632.                 return
  633.  
  634.             print "got devices: %s" % devices
  635.  
  636.         def get_devices_error (self, conn, exc):
  637.             if conn != self.conn:
  638.                 print "Ignoring stale error"
  639.                 return
  640.  
  641.             print "devices error: %s" % repr (exc)
  642.  
  643.         def cancel_clicked (self, button):
  644.             print "Cancel job..."
  645.             self.conn.cancelJob (1,
  646.                                  reply_handler=self.job_canceled,
  647.                                  error_handler=self.cancel_job_error)
  648.  
  649.         def job_canceled (self, conn, none):
  650.             if conn != self.conn:
  651.                 print "Ignoring stale reply for %s" % conn
  652.                 return
  653.  
  654.             print "Job canceled"
  655.  
  656.         def cancel_job_error (self, conn, exc):
  657.             if conn != self.conn:
  658.                 print "Ignoring stale error for %s" % conn
  659.                 return
  660.  
  661.             print "cancel error: %s" % repr (exc)
  662.  
  663.         def get_file_clicked (self, button):
  664.             self.my_file = file ("/tmp/foo", "w")
  665.             self.conn.getFile ("/admin/conf/cupsd.conf", file=self.my_file,
  666.                                reply_handler=self.got_file,
  667.                                error_handler=self.get_file_error)
  668.  
  669.         def got_file (self, conn, none):
  670.             if conn != self.conn:
  671.                 print "Ignoring stale reply for %s" % conn
  672.                 return
  673.  
  674.             print "Got file"
  675.  
  676.         def get_file_error (self, conn, exc):
  677.             if conn != self.conn:
  678.                 print "Ignoring stale error"
  679.                 return
  680.  
  681.             print "get file error: %s" % repr (exc)
  682.  
  683.         def harmless_clicked (self, button):
  684.             self.conn.getJobs (reply_handler=self.got_jobs,
  685.                                error_handler=self.get_jobs_error)
  686.  
  687.         def got_jobs (self, conn, result):
  688.             if conn != self.conn:
  689.                 print "Ignoring stale reply from %s" % repr (conn)
  690.                 return
  691.             print result
  692.  
  693.         def get_jobs_error (self, exc):
  694.             print "get jobs error: %s" % repr (exc)
  695.  
  696.     UI ()
  697.     gtk.main ()
  698.