root / pykota / trunk / bin / pkprinters @ 3073

Revision 3073, 18.1 kB (checked in by jerome, 17 years ago)

Now pkprinters doesn't modify CUPS' printers.conf if the -C | --cups
command line switch is not used.

  • 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 | --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 main(self, names, options) :
171        """Manage printers."""
172        if (not self.config.isAdmin) and (not options["list"]) :
173            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
174           
175        docups = options["cups"]
176       
177        if not options["list"] :   
178            percent = Percent(self)
179           
180        if not options["add"] :
181            if not options["list"] :
182                percent.display("%s..." % _("Extracting datas"))
183            if not names :      # NB : can't happen for --delete because it's catched earlier
184                names = ["*"]
185            printers = self.storage.getMatchingPrinters(",".join(names))
186            if not printers :
187                if not options["list"] :
188                    percent.display("\n")
189                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
190            if not options["list"] :   
191                percent.setSize(len(printers))
192               
193        if options["list"] :
194            for printer in printers :
195                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
196                print "%s [%s] (%s + #*%s)" % \
197                      (printer.Name, printer.Description, printer.PricePerJob, \
198                       printer.PricePerPage)
199                print "    %s" % (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF")))
200                print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
201                print "    %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO")))
202                if parents : 
203                    print "    %s %s" % (_("in"), parents)
204                print   
205        elif options["delete"] :   
206            percent.display("\n%s..." % _("Deletion"))
207            self.storage.deleteManyPrinters(printers)
208            if docups :
209                percent.display("\n%s...\n" % _("Rerouting printers to CUPS"))
210                for printer in printers :
211                    uri = self.getPrinterDeviceURI(printer.Name)
212                    if self.isPrinterCaptured(deviceuri=uri) :
213                         newuri = uri.replace("cupspykota:", "")
214                         if newuri.startswith("//") :
215                             newuri = newuri[2:]
216                         self.regainPriv() # to avoid having to enter password.
217                         os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
218                         self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
219                         self.dropPriv()   
220                    percent.oneMore()
221            percent.display("\n")
222        else :
223            if options["groups"] :       
224                printersgroups = self.storage.getMatchingPrinters(options["groups"])
225                if not printersgroups :
226                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
227            else :         
228                printersgroups = []
229                   
230            if options["charge"] :
231                try :
232                    charges = [float(part) for part in options["charge"].split(',', 1)]
233                except ValueError :   
234                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
235                else :   
236                    if len(charges) > 2 :
237                        charges = charges[:2]
238                    if len(charges) != 2 :
239                        charges = [charges[0], None]
240                    (perpage, perjob) = charges
241            else :       
242                charges = perpage = perjob = None
243                   
244            if options["maxjobsize"] :       
245                try :
246                    maxjobsize = int(options["maxjobsize"])
247                    if maxjobsize < 0 :
248                        raise ValueError
249                except ValueError :   
250                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
251            else :       
252                maxjobsize = None
253                   
254            description = options["description"]
255            if description :
256                description = description.strip()
257               
258            nopassthrough = options["nopassthrough"]   
259            passthrough = options["passthrough"]
260            remove = options["remove"]
261            skipexisting = options["skipexisting"]
262            self.storage.beginTransaction()
263            try :
264                if options["add"] :   
265                    percent.display("%s...\n" % _("Creation"))
266                    percent.setSize(len(names))
267                    for pname in names :
268                        if self.isValidName(pname) :
269                            printer = StoragePrinter(self.storage, pname)
270                            self.modifyPrinter(printer, charges, perpage, perjob, \
271                                           description, passthrough, \
272                                           nopassthrough, maxjobsize)
273                            oldprinter = self.storage.addPrinter(printer)               
274                           
275                            if docups :
276                                 uri = self.getPrinterDeviceURI(printer.Name)
277                                 if not self.isPrinterCaptured(deviceuri=uri) :
278                                      newuri = "cupspykota://%s" % uri
279                                      self.regainPriv() # to avoid having to enter password.
280                                      os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
281                                      self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
282                                      self.dropPriv()
283                                     
284                            if oldprinter is not None :
285                                if skipexisting :
286                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
287                                else :   
288                                    self.logdebug(_("Printer %s already exists, will be modified.") % pname)
289                                    self.modifyPrinter(oldprinter, charges, \
290                                               perpage, perjob, description, \
291                                               passthrough, nopassthrough, \
292                                               maxjobsize)
293                                    oldprinter.save()           
294                                    self.managePrintersGroups(printersgroups, oldprinter, remove)
295                            elif printersgroups :       
296                                self.managePrintersGroups(printersgroups, \
297                                                          self.storage.getPrinter(pname), \
298                                                          remove)
299                        else :   
300                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
301                        percent.oneMore()
302                else :       
303                    percent.display("\n%s...\n" % _("Modification"))
304                    for printer in printers :       
305                        self.modifyPrinter(printer, charges, perpage, perjob, \
306                                           description, passthrough, \
307                                           nopassthrough, maxjobsize)
308                        printer.save()   
309                        self.managePrintersGroups(printersgroups, printer, remove)
310                        percent.oneMore()
311            except :                   
312                self.storage.rollbackTransaction()
313                raise
314            else :   
315                self.storage.commitTransaction()
316               
317        if not options["list"] :
318            percent.done()
319                     
320if __name__ == "__main__" : 
321    retcode = 0
322    try :
323        short_options = "hvaCc:D:dg:lrsnpm:"
324        long_options = ["help", "version", "add", "cups", "charge=", "description=", \
325                        "delete", "groups=", "list", "remove", \
326                        "skipexisting", "passthrough", "nopassthrough", \
327                        "maxjobsize="]
328       
329        # Initializes the command line tool
330        manager = PKPrinters(doc=__doc__)
331        manager.deferredInit()
332       
333        # parse and checks the command line
334        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
335       
336        # sets long options
337        options["help"] = options["h"] or options["help"]
338        options["version"] = options["v"] or options["version"]
339        options["add"] = options["a"] or options["add"]
340        options["cups"] = options["C"] or options["cups"]
341        options["charge"] = options["c"] or options["charge"]
342        options["description"] = options["D"] or options["description"]
343        options["delete"] = options["d"] or options["delete"] 
344        options["groups"] = options["g"] or options["groups"]
345        options["list"] = options["l"] or options["list"]
346        options["remove"] = options["r"] or options["remove"]
347        options["skipexisting"] = options["s"] or options["skipexisting"]
348        options["maxjobsize"] = options["m"] or options["maxjobsize"]
349        options["passthrough"] = options["p"] or options["passthrough"]
350        options["nopassthrough"] = options["n"] or options["nopassthrough"]
351       
352        if options["help"] :
353            manager.display_usage_and_quit()
354        elif options["version"] :
355            manager.display_version_and_quit()
356        elif (options["delete"] and (options["add"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
357           or (options["skipexisting"] and not options["add"]) \
358           or (options["list"] and (options["add"] or options["delete"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
359           or (options["passthrough"] and options["nopassthrough"]) \
360           or (options["remove"] and options["add"]) :
361            raise PyKotaCommandLineError, _("incompatible options, see help.")
362        elif options["remove"] and not options["groups"] :
363            raise PyKotaCommandLineError, _("You have to pass printer groups names on the command line")
364        elif (not args) and (options["add"] or options["delete"]) :
365            raise PyKotaCommandLineError, _("You have to pass printer names on the command line")
366        else :
367            retcode = manager.main(args, options)
368    except KeyboardInterrupt :       
369        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
370        retcode = -3
371    except PyKotaCommandLineError, msg :   
372        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
373        retcode = -2
374    except SystemExit :       
375        pass
376    except :
377        try :
378            manager.crashed("pkprinters failed")
379        except :   
380            crashed("pkprinters failed")
381        retcode = -1
382
383    try :
384        manager.storage.close()
385    except (TypeError, NameError, AttributeError) :   
386        pass
387       
388    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.