root / pykota / trunk / bin / pkbanner @ 3294

Revision 3294, 13.5 kB (checked in by jerome, 16 years ago)

Added modules to store utility functions and application
intialization code, which has nothing to do in classes.
Modified tool.py accordingly (far from being finished)
Use these new modules where necessary.
Now converts all command line arguments to unicode before
beginning to work. Added a proper logging method for already
encoded query strings.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: UTF-8 -*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
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 3 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, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24import sys
25import os
26import time
27import cStringIO
28import popen2
29
30try :
31    from reportlab.pdfgen import canvas
32    from reportlab.lib import pagesizes
33    from reportlab.lib.units import cm
34except ImportError :   
35    hasRL = False
36else :   
37    hasRL = True
38   
39try :
40    import PIL.Image 
41except ImportError :   
42    hasPIL = False
43else :   
44    hasPIL = True
45   
46import pykota.appinit
47from pykota.utils import *
48
49from pykota.errors import PyKotaToolError, PyKotaCommandLineError
50from pykota.tool import Tool, crashed, N_
51
52__doc__ = N_("""pkbanner v%(__version__)s (c) %(__years__)s %(__author__)s
53
54Generates banners.
55
56command line usage :
57
58  pkbanner  [options]  [more info]
59
60options :
61
62  -v | --version       Prints pkbanner's version number then exits.
63  -h | --help          Prints this message then exits.
64 
65  -l | --logo img      Use the image as the banner's logo. The logo will
66                       be drawn at the center top of the page. The default
67                       logo is /usr/share/pykota/logos/pykota.jpeg
68                       
69  -p | --pagesize sz   Sets sz as the page size. Most well known
70                       page sizes are recognized, like 'A4' or 'Letter'
71                       to name a few. The default size is A4.
72 
73  -s | --savetoner s   Sets the text luminosity factor to s%%. This can be
74                       used to save toner. The default value is 0, which
75                       means that no toner saving will be done.
76 
77  -u | --url u         Uses u as an url to be written at the bottom of
78                       the banner page. The default url is :
79                       http://www.pykota.com/
80 
81examples :                             
82
83  Using pkbanner directly from the command line is not recommended,
84  excepted for testing purposes. You should use pkbanner in the
85  'startingbanner' or 'endingbanner' directives in pykota.conf
86 
87    startingbanner: /usr/bin/pkbanner --logo="" --savetoner=75
88 
89      With such a setting in pykota.conf, all print jobs will be
90      prefixed with an A4 banner with no logo, and text luminosity will
91      be increased by 75%%. The PostScript output will be directly sent
92      to your printer.
93     
94  You'll find more examples in the sample configuration file included   
95  in PyKota.
96""")
97       
98class PyKotaBanner(Tool) :       
99    """A class for pkbanner."""
100    def getPageSize(self, pgsize) :
101        """Returns the correct page size or None if not found."""
102        try :
103            return getattr(pagesizes, pgsize.upper())
104        except AttributeError :   
105            try :
106                return getattr(pagesizes, pgsize.lower())
107            except AttributeError :
108                pass
109               
110    def getVar(self, varname) :           
111        """Extracts a variable from the environment and returns its value or 'Unknown' in the current locale."""
112        return os.environ.get(varname) or _("Unknown")
113       
114    def printVar(self, canvas, x, y, label, value, size, savetoner) :
115        """Outputs a variable onto the PDF canvas.
116       
117           Returns the number of points to substract to current Y coordinate.
118        """   
119        canvas.saveState()
120        canvas.setFont("Helvetica-Bold", size)
121        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 0) ] # Black * savetoner
122        canvas.setFillColorRGB(r, g, b)
123        message = "%s :" % _(label)
124        canvas.drawRightString(x, y, self.userCharsetToUTF8(message))
125        canvas.setFont("Courier-Bold", size)
126        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (1, 0, 0) ] # Red * savetoner
127        canvas.setFillColorRGB(r, g, b)
128        canvas.drawString(x + 0.5*cm, y, self.userCharsetToUTF8(value))
129        canvas.restoreState()
130        return (size + 4)
131   
132    def genPDF(self, pagesize, logo, url, text, savetoner) :
133        """Generates the banner in PDF format, return the PDF document as a string."""
134        document = cStringIO.StringIO()
135        c = canvas.Canvas(document, pagesize=pagesize, pageCompression=1)
136       
137        c.setAuthor("Jerome Alet")
138        c.setTitle("PyKota generated Banner")
139        c.setSubject("This is a print banner generated with PyKota")
140       
141        xcenter = pagesize[0] / 2.0
142        ycenter = pagesize[1] / 2.0
143                   
144        ypos = pagesize[1] - (2 * cm)           
145       
146        if logo :
147            try :   
148                imglogo = PIL.Image.open(logo)
149            except :   
150                self.printInfo("Unable to open image %s" % logo, "warn")
151            else :
152                (width, height) = imglogo.size
153                multi = float(width) / (8 * cm) 
154                width = float(width) / multi
155                height = float(height) / multi
156                xpos = xcenter - (width / 2.0)
157                ypos -= height
158                c.drawImage(logo, xpos, ypos, width, height)
159       
160        # New top
161        xpos = pagesize[0] / 5.0
162        ypos -= (1 * cm) + 20
163       
164        printername = self.getVar("PYKOTAPRINTERNAME")
165        username = self.getVar("PYKOTAUSERNAME")
166        accountbanner = self.config.getAccountBanner(printername)
167       
168        # Outputs the username
169        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 
170       
171        # Text   
172        if text :
173            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 
174       
175        # Printer and Job Id
176        job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID"))
177        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 
178       
179        # Current date (TODO : at the time the banner was printed ! Change this to job's submission date)
180        datetime = time.strftime("%c", time.localtime())
181        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 
182       
183        # Result of the print job
184        action = self.getVar("PYKOTAACTION")
185        if action == "ALLOW" :
186            action = _("Allowed")
187        elif action == "DENY" :   
188            action = _("Denied")
189        elif action == "WARN" :   
190            action = _("Allowed with Warning")
191        elif action == "PROBLEM" :   
192            # should never occur
193            action = _("Problem")
194        elif action == "CANCEL" :   
195            # should never occur
196            action = _("Cancelled")
197        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
198       
199        # skip some space
200        ypos -= 20
201       
202        # Outputs title and filename
203        # We put them at x=0.25*pagewidth so that the line is long enough to hold them
204        title = self.getVar("PYKOTATITLE")
205        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 
206       
207        filename = self.getVar("PYKOTAFILENAME")
208        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 
209       
210        # skip some space
211        ypos -= 20
212       
213        # Now outputs the user's account balance or page counter
214        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 
215        limitby = self.getVar("PYKOTALIMITBY")
216        if limitby == "balance" : 
217            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 
218        elif limitby == "quota" :
219            ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 
220            ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 
221            ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 
222        else :
223            if limitby == "noquota" :
224                msg = _("No Limit")
225            elif limitby == "nochange" :   
226                msg = _("No Accounting")
227            elif limitby == "noprint" :   
228                msg = _("Forbidden")
229            else :   
230                msg = _("Unknown")
231            ypos -= self.printVar(c, xcenter, ypos, _("Printing Mode"), msg, 14, savetoner)
232           
233        # URL
234        if url :
235            c.saveState()
236            c.setFont("Courier-Bold", 16)
237            (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 1) ] # Blue * savetoner
238            c.setFillColorRGB(r, g, b)
239            c.drawCentredString(xcenter, 2 * cm, url)
240            c.restoreState()
241       
242        c.showPage()
243        c.save()
244        return document.getvalue()
245       
246    def main(self, arguments, options) :
247        """Generates a banner."""
248        if not hasRL :
249            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
250        if not hasPIL :
251            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
252           
253        try :
254            savetoner = int(options["savetoner"])
255            if (savetoner < 0) or (savetoner > 99) :
256                raise ValueError, _("Allowed range is (0..99)")
257            savetoner /= 100.0   
258        except (TypeError, ValueError), msg :
259            self.printInfo(_("Invalid 'savetoner' option %s : %s") % (options["savetoner"], msg), "warn")
260            savetoner = 0.0
261           
262        pagesize = self.getPageSize(options["pagesize"])
263        if pagesize is None :
264            pagesize = self.getPageSize("a4")
265            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
266           
267        self.logdebug("Generating the banner in PDF format...")   
268        doc = self.genPDF(pagesize, 
269                          options["logo"].strip(), 
270                          options["url"].strip(), 
271                          " ".join(arguments).strip(), 
272                          savetoner)
273       
274        self.logdebug("Converting the banner to PostScript...")   
275        os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "")
276        child = popen2.Popen3("gs -q -dNOPAUSE -dBATCH -dPARANOIDSAFER -sDEVICE=pswrite -sOutputFile=- -")
277        try :
278            child.tochild.write(doc)
279            child.tochild.close()
280            sys.stdout.write(child.fromchild.read())
281            sys.stdout.flush()
282            child.fromchild.close()
283        except IOError, msg :   
284            self.printInfo("I/O Error %s" % msg, "error")
285        status = child.wait()
286        if os.WIFEXITED(status) :
287            status = os.WEXITSTATUS(status)
288        self.logdebug("PDF to PostScript converter exit code is %s" % str(status))
289        self.logdebug("Banner completed.")
290        return status
291
292if __name__ == "__main__" :
293    # TODO : --papertray : to print banners on a different paper (colored for example)
294    retcode = 0
295    try :
296        defaults = { \
297                     "savetoner" : "0", \
298                     "pagesize" : "a4", \
299                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
300                     "url" : "http://www.pykota.com/",
301                   }
302        short_options = "vhs:l:p:u:"
303        long_options = ["help", "version", "savetoner=", "pagesize=", "logo=", "url="]
304       
305        # Initializes the command line tool
306        banner = PyKotaBanner(doc=__doc__)
307        banner.deferredInit()
308       
309        # parse and checks the command line
310        (options, args) = banner.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=1)
311       
312        # sets long options
313        options["help"] = options["h"] or options["help"]
314        options["version"] = options["v"] or options["version"]
315        options["savetoner"] = options["s"] or options["savetoner"] or defaults["savetoner"]
316        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
317        options["url"] = options["u"] or options["url"] or defaults["url"]
318       
319        options["logo"] = options["l"] or options["logo"]
320        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
321            options["logo"] = defaults["logo"] 
322       
323        if options["help"] :
324            banner.display_usage_and_quit()
325        elif options["version"] :
326            banner.display_version_and_quit()
327        else :
328            retcode = banner.main(args, options)
329    except KeyboardInterrupt :       
330        logerr("\nInterrupted with Ctrl+C !\n")
331        retcode = -3
332    except PyKotaCommandLineError, msg :   
333        logerr("%s : %s\n" % (sys.argv[0], msg))
334        retcode = -2
335    except SystemExit :       
336        pass
337    except :
338        try :
339            banner.crashed("pkbanner failed")
340        except :   
341            crashed("pkbanner failed")
342        retcode = -1
343       
344    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.