root / pykota / trunk / bin / pkprinters @ 3288

Revision 3288, 18.2 kB (checked in by jerome, 16 years ago)

Moved all exceptions definitions to a dedicated module.

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