root / pykota / trunk / bin / pkprinters @ 3052

Revision 3052, 17.8 kB (checked in by jerome, 17 years ago)

Now pkprinters reroutes CUPS print queues through PyKota or
through CUPS only when adding or deleting printers in the
database.
Fixed some display uglyness in case no entry was found.

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