root / pykota / trunk / bin / pkbanner @ 3288

Revision 3288, 13.4 kB (checked in by jerome, 16 years ago)

Moved all exceptions definitions to a dedicated module.

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