root / pykota / trunk / bin / pkbanner @ 3260

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

Changed license to GNU GPL v3 or later.
Changed Python source encoding from ISO-8859-15 to UTF-8 (only ASCII
was used anyway).

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