Back to index

system-config-printer  1.3.9+20120706
ppdcache.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## Copyright (C) 2010, 2011, 2012 Red Hat, Inc.
00004 ## Authors:
00005 ##  Tim Waugh <twaugh@redhat.com>
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 asyncconn
00022 import cups
00023 import gobject
00024 import gtk
00025 import os
00026 import tempfile
00027 from debug import *
00028 
00029 cups.require ("1.9.50")
00030 
00031 class PPDCache:
00032     def __init__ (self, host=None, port=None, encryption=None):
00033         self._cups = None
00034         self._exc = None
00035         self._cache = dict()
00036         self._modtimes = dict()
00037         self._host = host
00038         self._port = port
00039         self._encryption = encryption
00040         self._queued = list()
00041         self._connecting = False
00042         debugprint ("+%s" % self)
00043 
00044     def __del__ (self):
00045         debugprint ("-%s" % self)
00046         if self._cups:
00047             self._cups.destroy ()
00048 
00049     def fetch_ppd (self, name, callback, check_uptodate=True):
00050         if check_uptodate and self._modtimes.has_key (name):
00051             # We have getPPD3 so we can check whether the PPD is up to
00052             # date.
00053             debugprint ("%s: check if %s is up to date" % (self, name))
00054             self._cups.getPPD3 (name,
00055                                 modtime=self._modtimes[name],
00056                                 reply_handler=lambda c, r:
00057                                     self._got_ppd3 (c, name, r, callback),
00058                                 error_handler=lambda c, r:
00059                                     self._got_ppd3 (c, name, r, callback))
00060             return
00061 
00062         try:
00063             f = self._cache[name]
00064         except RuntimeError, e:
00065             self._schedule_callback (callback, name, None, e)
00066             return
00067         except KeyError:
00068             if not self._cups:
00069                 self._queued.append ((name, callback))
00070                 if not self._connecting:
00071                     self._connect ()
00072 
00073                 return
00074 
00075             debugprint ("%s: fetch PPD for %s" % (self, name))
00076             self._cups.getPPD3 (name,
00077                                 reply_handler=lambda c, r:
00078                                     self._got_ppd3 (c, name, r, callback),
00079                                 error_handler=lambda c, r:
00080                                     self._got_ppd3 (c, name, r, callback))
00081             return
00082 
00083         # Copy from our file object to a new temporary file, create a
00084         # PPD object from it, then remove the file.  This way we don't
00085         # leave temporary files around even though we are caching...
00086         f.seek (0)
00087         (tmpfd, tmpfname) = tempfile.mkstemp ()
00088         tmpf = file (tmpfname, "w")
00089         tmpf.writelines (f.readlines ())
00090         del tmpf
00091         os.close (tmpfd)
00092         try:
00093             ppd = cups.PPD (tmpfname)
00094             os.unlink (tmpfname)
00095             self._schedule_callback (callback, name, ppd, None)
00096         except Exception, e:
00097             os.unlink (tmpfname)
00098             self._schedule_callback (callback, name, None, e)
00099 
00100     def _connect (self, callback=None):
00101         self._connecting = True
00102         asyncconn.Connection (host=self._host, port=self._port,
00103                               encryption=self._encryption,
00104                               reply_handler=self._connected,
00105                               error_handler=self._connected)
00106 
00107     def _got_ppd (self, connection, name, result, callback):
00108         if isinstance (result, Exception):
00109             self._schedule_callback (callback, name, result, None)
00110         else:
00111             # Store an open file object, then remove the actual file.
00112             # This way we don't leave temporary files around.
00113             self._cache[name] = file (result)
00114             debugprint ("%s: caching %s (fd %d)" % (self, result,
00115                                                     self._cache[name].fileno()))
00116             os.unlink (result)
00117             self.fetch_ppd (name, callback)
00118 
00119     def _got_ppd3 (self, connection, name, result, callback):
00120         (status, modtime, filename) = result
00121         if status in [cups.HTTP_OK, cups.HTTP_NOT_MODIFIED]:
00122             if status == cups.HTTP_NOT_MODIFIED:
00123                 # The file is no newer than the one we already have.
00124 
00125                 # CUPS before 1.5.3 created a temporary file in error
00126                 # in this situation (STR #4018) so remove that.
00127                 try:
00128                     os.unlink (filename)
00129                 except OSError:
00130                     pass
00131 
00132             elif status == cups.HTTP_OK:
00133                 # Our version of the file was older.  Cache the new version.
00134 
00135                 # Store an open file object, then remove the actual
00136                 # file.  This way we don't leave temporary files
00137                 # around.
00138                 try:
00139                     self._cache[name] = file (filename)
00140                     debugprint ("%s: caching %s (fd %d) "
00141                                 "(%s) - %s" % (self, filename,
00142                                                self._cache[name].fileno (),
00143                                                modtime, status))
00144                     os.unlink (filename)
00145                     self._modtimes[name] = modtime
00146                 except IOError:
00147                     # File disappeared?
00148                     debugprint ("%s: file %s disappeared? Unable to cache it"
00149                                 % (self, filename))
00150 
00151             # Now fetch it from our own cache.
00152             self.fetch_ppd (name, callback, check_uptodate=False)
00153         else:
00154             self._schedule_callback (callback, name,
00155                                      None, cups.HTTPError (status))
00156 
00157     def _connected (self, connection, exc):
00158         self._connecting = False
00159         if isinstance (exc, Exception):
00160             self._cups = None
00161             self._exc = exc
00162         else:
00163             self._cups = connection
00164 
00165         queued = self._queued
00166         self._queued = list()
00167         for name, callback in queued:
00168             self.fetch_ppd (name, callback)
00169 
00170     def _schedule_callback (self, callback, name, result, exc):
00171         def cb_func (callback, name, result, exc):
00172             gtk.gdk.threads_enter ()
00173             callback (name, result, exc)
00174             gtk.gdk.threads_leave ()
00175             return False
00176 
00177         gobject.idle_add (cb_func, callback, name, result, exc)
00178 
00179 if __name__ == "__main__":
00180     import sys
00181     from debug import *
00182     set_debugging (True)
00183     gobject.threads_init ()
00184     gtk.gdk.threads_init ()
00185     loop = gobject.MainLoop ()
00186 
00187     def signal (name, result, exc):
00188         debugprint ("**** %s" % name)
00189         debugprint (result)
00190         debugprint (exc)
00191 
00192     c = cups.Connection ()
00193     printers = c.getPrinters ()
00194     del c
00195 
00196     cache = PPDCache ()
00197     p = None
00198     for p in printers:
00199         cache.fetch_ppd (p, signal)
00200 
00201     if p:
00202         gobject.timeout_add_seconds (1, cache.fetch_ppd, p, signal)
00203         gobject.timeout_add_seconds (5, loop.quit)
00204     loop.run ()