Back to index

system-config-printer  1.3.9+20120706
openprinting.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2008, 2011 Red Hat, Inc.
00006 ## Copyright (C) 2008 Till Kamppeter <till.kamppeter@gmail.com>
00007 
00008 ## This program is free software; you can redistribute it and/or modify
00009 ## it under the terms of the GNU General Public License as published by
00010 ## the Free Software Foundation; either version 2 of the License, or
00011 ## (at your option) any later version.
00012 
00013 ## This program is distributed in the hope that it will be useful,
00014 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 ## GNU General Public License for more details.
00017 
00018 ## You should have received a copy of the GNU General Public License
00019 ## along with this program; if not, write to the Free Software
00020 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00021 
00022 import pycurl, urllib, platform, threading, tempfile, traceback
00023 import os, sys
00024 from xml.etree.ElementTree import XML
00025 from . import Device
00026 
00027 __all__ = ['OpenPrinting']
00028 
00029 def _normalize_space (text):
00030     result = text.strip ()
00031     result = result.replace ('\n', ' ')
00032     i = result.find ('  ')
00033     while i != -1:
00034         result = result.replace ('  ', ' ')
00035         i = result.find ('  ')
00036     return result
00037 
00038 class _QueryThread (threading.Thread):
00039     def __init__ (self, parent, parameters, callback, user_data=None):
00040         threading.Thread.__init__ (self)
00041         self.parent = parent
00042         self.parameters = parameters
00043         self.callback = callback
00044         self.user_data = user_data
00045         self.result = ""
00046 
00047         self.setDaemon (True)
00048 
00049     def run (self):
00050 
00051         # Callback function for pycURL collecting the data coming from
00052         # the web server
00053         def collect_data(result):
00054             self.result += result;
00055             return len(result)
00056 
00057         # CGI script to be executed
00058         query_command = "/query.cgi"
00059         # Headers for the post request
00060         headers = {"Content-type": "application/x-www-form-urlencoded",
00061                    "Accept": "text/plain"}
00062         params = ("%s&uilanguage=%s&locale=%s" %
00063                   (urllib.urlencode (self.parameters),
00064                    self.parent.language[0],
00065                    self.parent.language[0]))
00066         self.url = "https://%s%s?%s" % (self.parent.base_url, query_command, params)
00067         # Send request
00068         result = None
00069         self.result = ""
00070         status = 1
00071         try:
00072             curl = pycurl.Curl()
00073             curl.setopt(pycurl.SSL_VERIFYPEER, 1)
00074             curl.setopt(pycurl.SSL_VERIFYHOST, 2)
00075             curl.setopt(pycurl.WRITEFUNCTION, collect_data)
00076             curl.setopt(pycurl.URL, self.url)
00077             status = curl.perform()
00078             if status == None: status = 0
00079             if (status != 0):
00080                 self.result = sys.exc_info ()
00081         except:
00082             self.result = sys.exc_info ()
00083             if status == None: status = 0
00084 
00085         if self.callback != None:
00086             self.callback (status, self.user_data, self.result)
00087 
00088 class OpenPrinting:
00089     def __init__(self, language=None):
00090         """
00091         @param language: language, as given by the first element of
00092         locale.setlocale().
00093         @type language: string
00094         """
00095         if language == None:
00096             import locale
00097             try:
00098                 language = locale.getlocale(locale.LC_MESSAGES)
00099             except locale.Error, e:
00100                 language = 'C'
00101         self.language = language
00102 
00103         # XXX Read configuration file.
00104         self.base_url = "www.openprinting.org"
00105 
00106         # Restrictions on driver choices XXX Parameters to be taken from
00107         # config file
00108         self.onlyfree = 1
00109         self.onlymanufacturer = 0
00110 
00111     def cancelOperation(self, handle):
00112         """
00113         Cancel an operation.
00114 
00115         @param handle: query/operation handle
00116         """
00117         # Just prevent the callback.
00118         try:
00119             handle.callback = None
00120         except:
00121             pass
00122 
00123     def webQuery(self, parameters, callback, user_data=None):
00124         """
00125         Run a web query for a driver.
00126 
00127         @type parameters: dict
00128         @param parameters: URL parameters
00129         @type callback: function
00130         @param callback: callback function, taking (integer, user_data, string)
00131         parameters with the first parameter being the status code, zero for
00132         success
00133         @return: query handle
00134         """
00135         the_thread = _QueryThread (self, parameters, callback, user_data)
00136         the_thread.start()
00137         return the_thread
00138 
00139     def searchPrinters(self, searchterm, callback, user_data=None):
00140         """
00141         Search for printers using a search term.
00142 
00143         @type searchterm: string
00144         @param searchterm: search term
00145         @type callback: function
00146         @param callback: callback function, taking (integer, user_data, string)
00147         parameters with the first parameter being the status code, zero for
00148         success
00149         @return: query handle
00150         """
00151 
00152         def parse_result (status, data, result):
00153             (callback, user_data) = data
00154             if status != 0:
00155                 callback (status, user_data, result)
00156                 return
00157 
00158             status = 0
00159             printers = {}
00160             try:
00161                 root = XML (result)
00162                 # We store the printers as a dict of:
00163                 # foomatic_id: displayname
00164 
00165                 for printer in root.findall ("printer"):
00166                     id = printer.find ("id")
00167                     make = printer.find ("make")
00168                     model = printer.find ("model")
00169                     if id != None and make != None and model != None:
00170                         idtxt = id.text
00171                         maketxt = make.text
00172                         modeltxt = model.text
00173                         if idtxt and maketxt and modeltxt:
00174                             printers[idtxt] = maketxt + " " + modeltxt
00175             except:
00176                 status = 1
00177                 printers = sys.exc_info ()
00178 
00179             try:
00180                 callback (status, user_data, printers)
00181             except:
00182                 (type, value, tb) = sys.exc_info ()
00183                 tblast = traceback.extract_tb (tb, limit=None)
00184                 if len (tblast):
00185                     tblast = tblast[:len (tblast) - 1]
00186                 extxt = traceback.format_exception_only (type, value)
00187                 for line in traceback.format_tb(tb):
00188                     print (line.strip ())
00189                 print (extxt[0].strip ())
00190 
00191         # Common parameters for the request
00192         params = { 'type': 'printers',
00193                    'printer': searchterm,
00194                    'format': 'xml' }
00195         return self.webQuery(params, parse_result, (callback, user_data))
00196 
00197     def listDrivers(self, model, callback, user_data=None, extra_options=None):
00198         """
00199         Obtain a list of printer drivers.
00200 
00201         @type model: string or cupshelpers.Device
00202         @param model: foomatic printer model string or a cupshelpers.Device
00203         object
00204         @type callback: function
00205         @param callback: callback function, taking (integer, user_data, string)
00206         parameters with the first parameter being the status code, zero for
00207         success
00208         @type extra_options: string -> string dictionary
00209         @param extra_options: Additional search options, see
00210         http://www.linuxfoundation.org/en/OpenPrinting/Database/Query
00211         @return: query handle
00212         """
00213 
00214         def parse_result (status, data, result):
00215             (callback, user_data) = data
00216             if status != 0:
00217                 callback (status, user_data, result)
00218 
00219             try:
00220                 root = XML (result)
00221                 drivers = {}
00222                 # We store the drivers as a dict of:
00223                 # foomatic_id:
00224                 #   { 'name': name,
00225                 #     'url': url,
00226                 #     'supplier': supplier,
00227                 #     'license': short license string e.g. GPLv2,
00228                 #     'licensetext': license text (Plain text),
00229                 #     'nonfreesoftware': Boolean,
00230                 #     'thirdpartysupplied': Boolean,
00231                 #     'manufacturersupplied': Boolean,
00232                 #     'patents': Boolean,
00233                 #     'supportcontacts' (optional):
00234                 #       list of { 'name',
00235                 #                 'url',
00236                 #                 'level',
00237                 #               }
00238                 #     'shortdescription': short description,
00239                 #     'recommended': Boolean,
00240                 #     'functionality':
00241                 #       { 'text': integer percentage,
00242                 #         'lineart': integer percentage,
00243                 #         'graphics': integer percentage,
00244                 #         'photo': integer percentage,
00245                 #         'speed': integer percentage,
00246                 #       }
00247                 #     'packages' (optional):
00248                 #       { arch:
00249                 #         { file:
00250                 #           { 'url': url,
00251                 #             'realversion': upstream version string,
00252                 #             'version': packaged version string,
00253                 #             'release': package release string
00254                 #           }
00255                 #         }
00256                 #       }
00257                 #     'ppds' (optional):
00258                 #       URL string list
00259                 #   }
00260                 # There is more information in the raw XML, but this
00261                 # can be added to the Python structure as needed.
00262 
00263                 for driver in root.findall ('driver'):
00264                     id = driver.attrib.get ('id')
00265                     if id == None:
00266                         continue
00267 
00268                     dict = {}
00269                     for attribute in ['name', 'url', 'supplier', 'license',
00270                                       'shortdescription' ]:
00271                         element = driver.find (attribute)
00272                         if element != None and element.text != None:
00273                             dict[attribute] = _normalize_space (element.text)
00274 
00275                     element = driver.find ('licensetext')
00276                     if element != None:
00277                         dict['licensetext'] = element.text
00278 
00279                     for boolean in ['nonfreesoftware', 'recommended',
00280                                     'patents', 'thirdpartysupplied',
00281                                     'manufacturersupplied']:
00282                         dict[boolean] = driver.find (boolean) != None
00283 
00284                     # Make a 'freesoftware' tag for compatibility with
00285                     # how the OpenPrinting API used to work (see trac
00286                     # #74).
00287                     dict['freesoftware'] = not dict['nonfreesoftware']
00288 
00289                     supportcontacts = []
00290                     container = driver.find ('supportcontacts')
00291                     if container != None:
00292                         for sc in container.findall ('supportcontact'):
00293                             supportcontact = {}
00294                             if sc.text != None:
00295                                 supportcontact['name'] = \
00296                                     _normalize_space (sc.text)
00297                             else:
00298                                 supportcontact['name'] = ""
00299                             supportcontact['url'] = sc.attrib.get ('url')
00300                             supportcontact['level'] = sc.attrib.get ('level')
00301                             supportcontacts.append (supportcontact)
00302 
00303                     if supportcontacts:
00304                         dict['supportcontacts'] = supportcontacts
00305 
00306                     if not dict.has_key ('name') or not dict.has_key ('url'):
00307                         continue
00308 
00309                     container = driver.find ('functionality')
00310                     if container != None:
00311                         functionality = {}
00312                         for attribute in ['text', 'lineart', 'graphics',
00313                                           'photo', 'speed']:
00314                             element = container.find (attribute)
00315                             if element != None:
00316                                 functionality[attribute] = element.text
00317                         if functionality:
00318                             dict[container.tag] = functionality
00319 
00320                     packages = {}
00321                     container = driver.find ('packages')
00322                     if container != None:
00323                         for arch in container.getchildren ():
00324                             rpms = {}
00325                             for package in arch.findall ('package'):
00326                                 rpm = {}
00327                                 for attribute in ['realversion','version',
00328                                                   'release', 'url', 'pkgsys']:
00329                                     element = package.find (attribute)
00330                                     if element != None:
00331                                         rpm[attribute] = element.text
00332 
00333                                 repositories = package.find ('repositories')
00334                                 if repositories != None:
00335                                     for pkgsys in repositories.getchildren ():
00336                                         rpm.setdefault('repositories', {})[pkgsys.tag] = pkgsys.text
00337 
00338                                 rpms[package.attrib['file']] = rpm
00339                             packages[arch.tag] = rpms
00340 
00341                     if packages:
00342                         dict['packages'] = packages
00343 
00344                     ppds = []
00345                     container = driver.find ('ppds')
00346                     if container != None:
00347                         for each in container.getchildren ():
00348                             ppds.append (each.text)
00349 
00350                     if ppds:
00351                         dict['ppds'] = ppds
00352 
00353                     drivers[id] = dict
00354                 callback (0, user_data, drivers)
00355             except:
00356                 callback (1, user_data, sys.exc_info ())
00357 
00358         if isinstance(model, Device):
00359             model = model.id
00360 
00361         params = { 'type': 'drivers',
00362                    'moreinfo': '1',
00363                    'showprinterid': '1',
00364                    'onlynewestdriverpackages': '1',
00365                    'architectures': platform.machine(),
00366                    'noobsoletes': '1',
00367                    'onlyfree': str (self.onlyfree),
00368                    'onlymanufacturer': str (self.onlymanufacturer),
00369                    'printer': model,
00370                    'format': 'xml'}
00371         if extra_options:
00372             params.update(extra_options)
00373         return self.webQuery(params, parse_result, (callback, user_data))
00374 
00375 def _simple_gui ():
00376     import gtk, pprint
00377     gtk.gdk.threads_init ()
00378     class QueryApp:
00379         def __init__(self):
00380             self.openprinting = OpenPrinting()
00381             self.main = gtk.Dialog ("OpenPrinting query application",
00382                                     None,
00383                                     gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR,
00384                                     (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
00385                                      "Search", 10,
00386                                      "List", 20))
00387             self.main.set_border_width (6)
00388             self.main.vbox.set_spacing (2)
00389             vbox = gtk.VBox (False, 6)
00390             self.main.vbox.pack_start (vbox, True, True, 0)
00391             vbox.set_border_width (6)
00392             self.entry = gtk.Entry ()
00393             vbox.pack_start (self.entry, False, False, 6)
00394             sw = gtk.ScrolledWindow ()
00395             self.tv = gtk.TextView ()
00396             sw.add (self.tv)
00397             vbox.pack_start (sw, True, True, 6)
00398             self.main.connect ("response", self.response)
00399             self.main.show_all ()
00400 
00401         def response (self, dialog, response):
00402             if (response == gtk.RESPONSE_CLOSE or
00403                 response == gtk.RESPONSE_DELETE_EVENT):
00404                 gtk.main_quit ()
00405 
00406             if response == 10:
00407                 # Run a query.
00408                 self.openprinting.searchPrinters (self.entry.get_text (),
00409                                                   self.search_printers_callback)
00410 
00411             if response == 20:
00412                 self.openprinting.listDrivers (self.entry.get_text (),
00413                                                self.list_drivers_callback)
00414 
00415         def search_printers_callback (self, status, user_data, printers):
00416             if status != 0:
00417                 raise printers[1]
00418 
00419             text = ""
00420             for printer in printers.values ():
00421                 text += printer + "\n"
00422             gtk.gdk.threads_enter ()
00423             self.tv.get_buffer ().set_text (text)
00424             gtk.gdk.threads_leave ()
00425 
00426         def list_drivers_callback (self, status, user_data, drivers):
00427             if status != 0:
00428                 raise drivers[1]
00429 
00430             text = pprint.pformat (drivers)
00431             gtk.gdk.threads_enter ()
00432             self.tv.get_buffer ().set_text (text)
00433             gtk.gdk.threads_leave ()
00434 
00435         def query_callback (self, status, user_data, result):
00436             gtk.gdk.threads_enter ()
00437             self.tv.get_buffer ().set_text (str (result))
00438             file ("result.xml", "w").write (str (result))
00439             gtk.gdk.threads_leave ()
00440 
00441     q = QueryApp()
00442     gtk.main ()