root / pykota / trunk / bin / pkprinters @ 3335

Revision 3295, 18.2 kB (checked in by jerome, 17 years ago)

Made the CGI scripts work again.
Moved even more functions to the utils module.
Removed the cgifuncs module, moved (and changed) content into utils.
If no output encoding defined, use UTF-8 : when wget is used to try
the CGI scripts, it doesn't set by default the accepted charset and
language headers.

  • 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 os
25import sys
26import pwd
27
28import pykota.appinit
29from pykota.utils import *
30
31from pykota.errors import PyKotaCommandLineError
32from pykota.tool import Percent, PyKotaTool
33from pykota.storage import StoragePrinter
34
35from pkipplib import pkipplib
36
37__doc__ = N_("""pkprinters v%(__version__)s (c) %(__years__)s %(__author__)s
38
39A Printers Manager for PyKota.
40
41command line usage :
42
43  pkprinters [options] printer1 printer2 printer3 ... printerN
44
45options :
46
47  -v | --version       Prints pkprinters's version number then exits.
48  -h | --help          Prints this message then exits.
49 
50  -a | --add           Adds printers if they don't exist on the Quota
51                       Storage Server. If they exist, they are modified
52                       unless -s|--skipexisting is also used.
53                       
54  -d | --delete        Deletes printers from the quota storage.
55 
56  -D | --description d Adds a textual description to printers.
57
58  -C | --cups          Also modifies the DeviceURI in CUPS' printers.conf
59
60  -c | --charge p[,j]  Sets the price per page and per job to charge.
61                       Job price is optional.
62                       If both are to be set, separate them with a comma.
63                       Floating point and negative values are allowed.
64 
65  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer
66                       groups pg1, pg2, etc... which must already exist.
67                       A printer group is just like a normal printer,
68                       only that it is usually unknown from the printing
69                       system. Create printer groups exactly the same
70                       way that you create printers, then add other
71                       printers to them with this option.
72                       Accounting is done on a printer and on all
73                       the printer groups it belongs to, quota checking
74                       is done on a printer and on all the printer groups
75                       it belongs to.
76                       If the --remove option below is not used, the
77                       default action is to add printers to the specified
78                       printer groups.
79                       
80  -l | --list          List informations about the printer(s) and the
81                       printers groups it is a member of.
82                       
83  -r | --remove        In combination with the --groups option above,                       
84                       remove printers from the specified printers groups.
85                       
86  -s | --skipexisting  In combination with the --add option above, tells
87                       pkprinters to not modify existing printers.
88                       
89  -m | --maxjobsize s  Sets the maximum job size allowed on the printer
90                       to s pages.
91                       
92  -p | --passthrough   Activate passthrough mode for the printer. In this
93                       mode, users are allowed to print without any impact
94                       on their quota or account balance.
95                       
96  -n | --nopassthrough Deactivate passthrough mode for the printer.
97                       Without -p or -n, printers are created in
98                       normal mode, i.e. no passthrough.
99 
100  printer1 through printerN can contain wildcards if the --add option
101  is not set.
102 
103examples :                             
104
105  $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000
106 
107  Will create three printers named hp2100, hp2200 and hp8000.
108  Their price per page will be set at 0.05 unit, and their price
109  per job will be set at 0.1 unit. Units are in your own currency,
110  or whatever you want them to mean.
111  All of their descriptions will be set to the string "HP Printer".
112  If any of these printers already exists, it will also be modified
113  unless the -s|--skipexisting command line option is also used.
114           
115  $ pkprinters --delete "*"
116 
117  This will completely delete all printers and associated quota information,
118  as well as their job history. USE WITH CARE !
119 
120  $ pkprinters --groups Laser,HP "hp*"
121 
122  This will put all printers which name matches "hp*" into printers groups
123  Laser and HP, which MUST already exist.
124 
125  $ pkprinters --groups LexMark --remove hp2200
126 
127  This will remove the hp2200 printer from the LexMark printer group.
128""")
129       
130class PKPrinters(PyKotaTool) :       
131    """A class for a printers manager."""
132    def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
133        if charges :
134            printer.setPrices(perpage, perjob)   
135        if description is not None :        # NB : "" is allowed !
136            printer.setDescription(description)
137        if nopassthrough :   
138            printer.setPassThrough(False)
139        if passthrough :   
140            printer.setPassThrough(True)
141        if maxjobsize is not None :
142            printer.setMaxJobSize(maxjobsize)
143           
144    def managePrintersGroups(self, pgroups, printer, remove) :       
145        """Manage printer group membership."""
146        for pgroup in pgroups :
147            if remove :
148                pgroup.delPrinterFromGroup(printer)
149            else :
150                pgroup.addPrinterToGroup(printer)   
151               
152    def getPrinterDeviceURI(self, printername) :           
153        """Returns the Device URI attribute for a particular printer."""
154        if not printername :
155            return ""
156        cups = pkipplib.CUPS()
157        req = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
158        req.operation["printer-uri"] = ("uri", cups.identifierToURI("printers", printername))
159        try :
160            return cups.doRequest(req).printer["device-uri"][0][1]
161        except :   
162            self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
163            return ""
164       
165    def isPrinterCaptured(self, printername=None, deviceuri=None) :
166        """Returns True if the printer is already redirected through PyKota's backend, else False."""
167        if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 :
168            return True
169        else :   
170            return False
171       
172    def reroutePrinterThroughPyKota(self, printer) :   
173        """Reroutes a CUPS printer through PyKota."""
174        uri = self.getPrinterDeviceURI(printer.Name)
175        if uri and (not self.isPrinterCaptured(deviceuri=uri)) :
176             newuri = "cupspykota://%s" % uri
177             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
178             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
179             
180    def deroutePrinterFromPyKota(self, printer) :   
181        """Deroutes a PyKota printer through CUPS only."""
182        uri = self.getPrinterDeviceURI(printer.Name)
183        if uri and self.isPrinterCaptured(deviceuri=uri) :
184             newuri = uri.replace("cupspykota:", "")
185             if newuri.startswith("//") :
186                 newuri = newuri[2:]
187             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
188             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
189                                     
190    def main(self, names, options) :
191        """Manage printers."""
192        if (not self.config.isAdmin) and (not options["list"]) :
193            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
194           
195        docups = options["cups"]
196       
197        if not options["list"] :   
198            percent = Percent(self)
199           
200        if not options["add"] :
201            if not options["list"] :
202                percent.display("%s..." % _("Extracting datas"))
203            if not names :      # NB : can't happen for --delete because it's catched earlier
204                names = ["*"]
205            printers = self.storage.getMatchingPrinters(",".join(names))
206            if not printers :
207                if not options["list"] :
208                    percent.display("\n")
209                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
210            if not options["list"] :   
211                percent.setSize(len(printers))
212               
213        if options["list"] :
214            for printer in printers :
215                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
216                print "%s [%s] (%s + #*%s)" % \
217                      (printer.Name, printer.Description, printer.PricePerJob, \
218                       printer.PricePerPage)
219                print "    %s" % (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF")))
220                print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
221                print "    %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO")))
222                if parents : 
223                    print "    %s %s" % (_("in"), parents)
224                print   
225        elif options["delete"] :   
226            percent.display("\n%s..." % _("Deletion"))
227            self.storage.deleteManyPrinters(printers)
228            percent.display("\n")
229            if docups :
230                percent.display("%s...\n" % _("Rerouting printers to CUPS"))
231                for printer in printers :
232                    self.deroutePrinterFromPyKota(printer)
233                    percent.oneMore()
234        else :
235            if options["groups"] :       
236                printersgroups = self.storage.getMatchingPrinters(options["groups"])
237                if not printersgroups :
238                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
239            else :         
240                printersgroups = []
241                   
242            if options["charge"] :
243                try :
244                    charges = [float(part) for part in options["charge"].split(',', 1)]
245                except ValueError :   
246                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
247                else :   
248                    if len(charges) > 2 :
249                        charges = charges[:2]
250                    if len(charges) != 2 :
251                        charges = [charges[0], None]
252                    (perpage, perjob) = charges
253            else :       
254                charges = perpage = perjob = None
255                   
256            if options["maxjobsize"] :       
257                try :
258                    maxjobsize = int(options["maxjobsize"])
259                    if maxjobsize < 0 :
260                        raise ValueError
261                except ValueError :   
262                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
263            else :       
264                maxjobsize = None
265                   
266            description = options["description"]
267            if description :
268                description = description.strip()
269               
270            nopassthrough = options["nopassthrough"]   
271            passthrough = options["passthrough"]
272            remove = options["remove"]
273            skipexisting = options["skipexisting"]
274            self.storage.beginTransaction()
275            try :
276                if options["add"] :   
277                    percent.display("%s...\n" % _("Creation"))
278                    percent.setSize(len(names))
279                    for pname in names :
280                        if self.isValidName(pname) :
281                            printer = StoragePrinter(self.storage, pname)
282                            self.modifyPrinter(printer, charges, perpage, perjob, \
283                                           description, passthrough, \
284                                           nopassthrough, maxjobsize)
285                            oldprinter = self.storage.addPrinter(printer)               
286                           
287                            if docups :
288                                 self.reroutePrinterThroughPyKota(printer)
289                                     
290                            if oldprinter is not None :
291                                if skipexisting :
292                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
293                                else :   
294                                    self.logdebug(_("Printer %s already exists, will be modified.") % pname)
295                                    self.modifyPrinter(oldprinter, charges, \
296                                               perpage, perjob, description, \
297                                               passthrough, nopassthrough, \
298                                               maxjobsize)
299                                    oldprinter.save()           
300                                    self.managePrintersGroups(printersgroups, oldprinter, remove)
301                            elif printersgroups :       
302                                self.managePrintersGroups(printersgroups, \
303                                                          self.storage.getPrinter(pname), \
304                                                          remove)
305                        else :   
306                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
307                        percent.oneMore()
308                else :       
309                    percent.display("\n%s...\n" % _("Modification"))
310                    for printer in printers :       
311                        self.modifyPrinter(printer, charges, perpage, perjob, \
312                                           description, passthrough, \
313                                           nopassthrough, maxjobsize)
314                        printer.save()   
315                        self.managePrintersGroups(printersgroups, printer, remove)
316                        if docups :
317                            self.reroutePrinterThroughPyKota(printer)
318                        percent.oneMore()
319            except :                   
320                self.storage.rollbackTransaction()
321                raise
322            else :   
323                self.storage.commitTransaction()
324               
325        if not options["list"] :
326            percent.done()
327                     
328if __name__ == "__main__" : 
329    retcode = 0
330    try :
331        short_options = "hvaCc:D:dg:lrsnpm:"
332        long_options = ["help", "version", "add", "cups", "charge=", "description=", \
333                        "delete", "groups=", "list", "remove", \
334                        "skipexisting", "passthrough", "nopassthrough", \
335                        "maxjobsize="]
336       
337        # Initializes the command line tool
338        manager = PKPrinters(doc=__doc__)
339        manager.deferredInit()
340       
341        # parse and checks the command line
342        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
343       
344        # sets long options
345        options["help"] = options["h"] or options["help"]
346        options["version"] = options["v"] or options["version"]
347        options["add"] = options["a"] or options["add"]
348        options["cups"] = options["C"] or options["cups"]
349        options["charge"] = options["c"] or options["charge"]
350        options["description"] = options["D"] or options["description"]
351        options["delete"] = options["d"] or options["delete"] 
352        options["groups"] = options["g"] or options["groups"]
353        options["list"] = options["l"] or options["list"]
354        options["remove"] = options["r"] or options["remove"]
355        options["skipexisting"] = options["s"] or options["skipexisting"]
356        options["maxjobsize"] = options["m"] or options["maxjobsize"]
357        options["passthrough"] = options["p"] or options["passthrough"]
358        options["nopassthrough"] = options["n"] or options["nopassthrough"]
359       
360        if options["help"] :
361            manager.display_usage_and_quit()
362        elif options["version"] :
363            manager.display_version_and_quit()
364        elif (options["delete"] and (options["add"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
365           or (options["skipexisting"] and not options["add"]) \
366           or (options["list"] and (options["add"] or options["delete"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
367           or (options["passthrough"] and options["nopassthrough"]) \
368           or (options["remove"] and options["add"]) :
369            raise PyKotaCommandLineError, _("incompatible options, see help.")
370        elif options["remove"] and not options["groups"] :
371            raise PyKotaCommandLineError, _("You have to pass printer groups names on the command line")
372        elif (not args) and (options["add"] or options["delete"]) :
373            raise PyKotaCommandLineError, _("You have to pass printer names on the command line")
374        else :
375            retcode = manager.main(args, options)
376    except KeyboardInterrupt :       
377        logerr("\nInterrupted with Ctrl+C !\n")
378        retcode = -3
379    except PyKotaCommandLineError, msg :   
380        logerr("%s : %s\n" % (sys.argv[0], msg))
381        retcode = -2
382    except SystemExit :       
383        pass
384    except :
385        try :
386            manager.crashed("pkprinters failed")
387        except :   
388            crashed("pkprinters failed")
389        retcode = -1
390
391    try :
392        manager.storage.close()
393    except (TypeError, NameError, AttributeError) :   
394        pass
395       
396    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.