root / pykota / branches / 1.26_fixes / bin / pkprinters

Revision 3394, 18.4 kB (checked in by jerome, 16 years ago)

Backported a fix for pkprinters. Fixes #18.

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