root / pykota / trunk / bin / pkprinters @ 3275

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

Updated copyright years.
Changed some remaining ISO-8859-15 markers to UTF-8 in Python source code.
Added missing source encoding markers.
Added missing copyright messages.

  • 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
28from pykota.tool import Percent, PyKotaTool, PyKotaCommandLineError, crashed, N_
29from pykota.storage import StoragePrinter
30
31from pkipplib import pkipplib
32
33__doc__ = N_("""pkprinters v%(__version__)s (c) %(__years__)s %(__author__)s
34
35A Printers Manager for PyKota.
36
37command line usage :
38
39  pkprinters [options] printer1 printer2 printer3 ... printerN
40
41options :
42
43  -v | --version       Prints pkprinters's version number then exits.
44  -h | --help          Prints this message then exits.
45 
46  -a | --add           Adds printers if they don't exist on the Quota
47                       Storage Server. If they exist, they are modified
48                       unless -s|--skipexisting is also used.
49                       
50  -d | --delete        Deletes printers from the quota storage.
51 
52  -D | --description d Adds a textual description to printers.
53
54  -C | --cups          Also modifies the DeviceURI in CUPS' printers.conf
55
56  -c | --charge p[,j]  Sets the price per page and per job to charge.
57                       Job price is optional.
58                       If both are to be set, separate them with a comma.
59                       Floating point and negative values are allowed.
60 
61  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer
62                       groups pg1, pg2, etc... which must already exist.
63                       A printer group is just like a normal printer,
64                       only that it is usually unknown from the printing
65                       system. Create printer groups exactly the same
66                       way that you create printers, then add other
67                       printers to them with this option.
68                       Accounting is done on a printer and on all
69                       the printer groups it belongs to, quota checking
70                       is done on a printer and on all the printer groups
71                       it belongs to.
72                       If the --remove option below is not used, the
73                       default action is to add printers to the specified
74                       printer groups.
75                       
76  -l | --list          List informations about the printer(s) and the
77                       printers groups it is a member of.
78                       
79  -r | --remove        In combination with the --groups option above,                       
80                       remove printers from the specified printers groups.
81                       
82  -s | --skipexisting  In combination with the --add option above, tells
83                       pkprinters to not modify existing printers.
84                       
85  -m | --maxjobsize s  Sets the maximum job size allowed on the printer
86                       to s pages.
87                       
88  -p | --passthrough   Activate passthrough mode for the printer. In this
89                       mode, users are allowed to print without any impact
90                       on their quota or account balance.
91                       
92  -n | --nopassthrough Deactivate passthrough mode for the printer.
93                       Without -p or -n, printers are created in
94                       normal mode, i.e. no passthrough.
95 
96  printer1 through printerN can contain wildcards if the --add option
97  is not set.
98 
99examples :                             
100
101  $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000
102 
103  Will create three printers named hp2100, hp2200 and hp8000.
104  Their price per page will be set at 0.05 unit, and their price
105  per job will be set at 0.1 unit. Units are in your own currency,
106  or whatever you want them to mean.
107  All of their descriptions will be set to the string "HP Printer".
108  If any of these printers already exists, it will also be modified
109  unless the -s|--skipexisting command line option is also used.
110           
111  $ pkprinters --delete "*"
112 
113  This will completely delete all printers and associated quota information,
114  as well as their job history. USE WITH CARE !
115 
116  $ pkprinters --groups Laser,HP "hp*"
117 
118  This will put all printers which name matches "hp*" into printers groups
119  Laser and HP, which MUST already exist.
120 
121  $ pkprinters --groups LexMark --remove hp2200
122 
123  This will remove the hp2200 printer from the LexMark printer group.
124""")
125       
126class PKPrinters(PyKotaTool) :       
127    """A class for a printers manager."""
128    def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
129        if charges :
130            printer.setPrices(perpage, perjob)   
131        if description is not None :        # NB : "" is allowed !
132            printer.setDescription(description)
133        if nopassthrough :   
134            printer.setPassThrough(False)
135        if passthrough :   
136            printer.setPassThrough(True)
137        if maxjobsize is not None :
138            printer.setMaxJobSize(maxjobsize)
139           
140    def managePrintersGroups(self, pgroups, printer, remove) :       
141        """Manage printer group membership."""
142        for pgroup in pgroups :
143            if remove :
144                pgroup.delPrinterFromGroup(printer)
145            else :
146                pgroup.addPrinterToGroup(printer)   
147               
148    def getPrinterDeviceURI(self, printername) :           
149        """Returns the Device URI attribute for a particular printer."""
150        if not printername :
151            return ""
152        cups = pkipplib.CUPS()
153        req = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
154        req.operation["printer-uri"] = ("uri", cups.identifierToURI("printers", printername))
155        try :
156            return cups.doRequest(req).printer["device-uri"][0][1]
157        except :   
158            self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
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 reroutePrinterThroughPyKota(self, printer) :   
169        """Reroutes a CUPS printer through PyKota."""
170        uri = self.getPrinterDeviceURI(printer.Name)
171        if uri and (not self.isPrinterCaptured(deviceuri=uri)) :
172             newuri = "cupspykota://%s" % uri
173             self.regainPriv() # to avoid having to enter password.
174             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
175             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
176             self.dropPriv()
177             
178    def deroutePrinterFromPyKota(self, printer) :   
179        """Deroutes a PyKota printer through CUPS only."""
180        uri = self.getPrinterDeviceURI(printer.Name)
181        if uri and self.isPrinterCaptured(deviceuri=uri) :
182             newuri = uri.replace("cupspykota:", "")
183             if newuri.startswith("//") :
184                 newuri = newuri[2:]
185             self.regainPriv() # to avoid having to enter password.
186             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
187             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
188             self.dropPriv()   
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        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
378        retcode = -3
379    except PyKotaCommandLineError, msg :   
380        sys.stderr.write("%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.