root / pykota / trunk / bin / pkprinters @ 3260

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

Changed license to GNU GPL v3 or later.
Changed Python source encoding from ISO-8859-15 to UTF-8 (only ASCII
was used anyway).

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