root / pykota / trunk / bin / pkprinters @ 3413

Revision 3413, 17.2 kB (checked in by jerome, 16 years ago)

Removed unnecessary spaces at EOL.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[1330]1#! /usr/bin/env python
[3411]2# -*- coding: utf-8 -*-*-
[1330]3#
[3260]4# PyKota : Print Quotas for CUPS
[1330]5#
[3275]6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[3260]7# This program is free software: you can redistribute it and/or modify
[1330]8# it under the terms of the GNU General Public License as published by
[3260]9# the Free Software Foundation, either version 3 of the License, or
[1330]10# (at your option) any later version.
[3413]11#
[1330]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.
[3413]16#
[1330]17# You should have received a copy of the GNU General Public License
[3260]18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[1330]19#
20# $Id$
21#
[2028]22#
[1330]23
[1821]24import os
[1330]25import sys
[1821]26import pwd
[1330]27
[3294]28import pykota.appinit
29from pykota.utils import *
30
[3288]31from pykota.errors import PyKotaCommandLineError
[3295]32from pykota.tool import Percent, PyKotaTool
[2768]33from pykota.storage import StoragePrinter
[1330]34
[3051]35from pkipplib import pkipplib
36
[2344]37__doc__ = N_("""pkprinters v%(__version__)s (c) %(__years__)s %(__author__)s
[2267]38
[1330]39A Printers Manager for PyKota.
40
41command line usage :
42
43  pkprinters [options] printer1 printer2 printer3 ... printerN
44
45options :
46
47  -v | --version       Prints pkprinters's version number then exits.
48  -h | --help          Prints this message then exits.
[3413]49
50  -a | --add           Adds printers if they don't exist on the Quota
[1453]51                       Storage Server. If they exist, they are modified
52                       unless -s|--skipexisting is also used.
[3413]53
[1331]54  -d | --delete        Deletes printers from the quota storage.
[3413]55
[1853]56  -D | --description d Adds a textual description to printers.
[3073]57
58  -C | --cups          Also modifies the DeviceURI in CUPS' printers.conf
59
[1330]60  -c | --charge p[,j]  Sets the price per page and per job to charge.
61                       Job price is optional.
62                       If both are to be set, separate them with a comma.
63                       Floating point and negative values are allowed.
[3413]64
65  -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer
[1332]66                       groups pg1, pg2, etc... which must already exist.
[1330]67                       A printer group is just like a normal printer,
68                       only that it is usually unknown from the printing
69                       system. Create printer groups exactly the same
[3413]70                       way that you create printers, then add other
[1330]71                       printers to them with this option.
72                       Accounting is done on a printer and on all
73                       the printer groups it belongs to, quota checking
74                       is done on a printer and on all the printer groups
75                       it belongs to.
[3413]76                       If the --remove option below is not used, the
[1332]77                       default action is to add printers to the specified
78                       printer groups.
[3413]79
[1437]80  -l | --list          List informations about the printer(s) and the
81                       printers groups it is a member of.
[3413]82
83  -r | --remove        In combination with the --groups option above,
[1332]84                       remove printers from the specified printers groups.
[3413]85
[1453]86  -s | --skipexisting  In combination with the --add option above, tells
87                       pkprinters to not modify existing printers.
[3413]88
[2465]89  -m | --maxjobsize s  Sets the maximum job size allowed on the printer
90                       to s pages.
[3413]91
[2465]92  -p | --passthrough   Activate passthrough mode for the printer. In this
93                       mode, users are allowed to print without any impact
94                       on their quota or account balance.
[3413]95
[2465]96  -n | --nopassthrough Deactivate passthrough mode for the printer.
[3413]97                       Without -p or -n, printers are created in
[2465]98                       normal mode, i.e. no passthrough.
[3413]99
100  printer1 through printerN can contain wildcards if the --add option
[1330]101  is not set.
102
[3413]103examples :
104
[1582]105  $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000
[3413]106
[1452]107  Will create three printers named hp2100, hp2200 and hp8000.
[1330]108  Their price per page will be set at 0.05 unit, and their price
109  per job will be set at 0.1 unit. Units are in your own currency,
110  or whatever you want them to mean.
[1582]111  All of their descriptions will be set to the string "HP Printer".
[3413]112  If any of these printers already exists, it will also be modified
[1582]113  unless the -s|--skipexisting command line option is also used.
[3413]114
[1330]115  $ pkprinters --delete "*"
[3413]116
[1330]117  This will completely delete all printers and associated quota information,
118  as well as their job history. USE WITH CARE !
[3413]119
[1330]120  $ pkprinters --groups Laser,HP "hp*"
[3413]121
122  This will put all printers which name matches "hp*" into printers groups
[1330]123  Laser and HP, which MUST already exist.
[3413]124
[1332]125  $ pkprinters --groups LexMark --remove hp2200
[3413]126
[1332]127  This will remove the hp2200 printer from the LexMark printer group.
[2344]128""")
[3413]129
130class PKPrinters(PyKotaTool) :
[2336]131    """A class for a printers manager."""
[2768]132    def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
133        if charges :
[3413]134            printer.setPrices(perpage, perjob)
[2768]135        if description is not None :        # NB : "" is allowed !
136            printer.setDescription(description)
[3413]137        if nopassthrough :
[2768]138            printer.setPassThrough(False)
[3413]139        if passthrough :
[2768]140            printer.setPassThrough(True)
141        if maxjobsize is not None :
142            printer.setMaxJobSize(maxjobsize)
[3413]143
144    def managePrintersGroups(self, pgroups, printer, remove) :
[2768]145        """Manage printer group membership."""
146        for pgroup in pgroups :
147            if remove :
148                pgroup.delPrinterFromGroup(printer)
149            else :
[3413]150                pgroup.addPrinterToGroup(printer)
151
152    def getPrinterDeviceURI(self, printername) :
[3051]153        """Returns the Device URI attribute for a particular printer."""
[3052]154        if not printername :
155            return ""
[3051]156        cups = pkipplib.CUPS()
157        req = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
158        req.operation["printer-uri"] = ("uri", cups.identifierToURI("printers", printername))
159        try :
160            return cups.doRequest(req).printer["device-uri"][0][1]
[3413]161        except :
[3269]162            self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
[3051]163            return ""
[3413]164
[3052]165    def isPrinterCaptured(self, printername=None, deviceuri=None) :
[3051]166        """Returns True if the printer is already redirected through PyKota's backend, else False."""
[3052]167        if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 :
[3051]168            return True
[3413]169        else :
[3051]170            return False
[3413]171
172    def reroutePrinterThroughPyKota(self, printer) :
[3105]173        """Reroutes a CUPS printer through PyKota."""
174        uri = self.getPrinterDeviceURI(printer.Name)
[3269]175        if uri and (not self.isPrinterCaptured(deviceuri=uri)) :
[3105]176             newuri = "cupspykota://%s" % uri
177             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
178             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
[3413]179
180    def deroutePrinterFromPyKota(self, printer) :
[3105]181        """Deroutes a PyKota printer through CUPS only."""
182        uri = self.getPrinterDeviceURI(printer.Name)
[3269]183        if uri and self.isPrinterCaptured(deviceuri=uri) :
[3105]184             newuri = uri.replace("cupspykota:", "")
185             if newuri.startswith("//") :
186                 newuri = newuri[2:]
187             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
188             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
[3413]189
[1330]190    def main(self, names, options) :
191        """Manage printers."""
[3367]192        if not options["list"] :
193            self.adminOnly()
[3413]194
[3073]195        docups = options["cups"]
[3413]196
197        if not options["list"] :
[2783]198            percent = Percent(self)
[3413]199
[2768]200        if not options["add"] :
201            if not options["list"] :
[2789]202                percent.display("%s..." % _("Extracting datas"))
[2768]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 :
[3052]207                if not options["list"] :
208                    percent.display("\n")
[2768]209                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
[3413]210            if not options["list"] :
[2783]211                percent.setSize(len(printers))
[3413]212
[2768]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")))
[3051]221                print "    %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO")))
[3413]222                if parents :
[2768]223                    print "    %s %s" % (_("in"), parents)
[3413]224                print
225        elif options["delete"] :
[2783]226            percent.display("\n%s..." % _("Deletion"))
[2768]227            self.storage.deleteManyPrinters(printers)
[3105]228            percent.display("\n")
[3073]229            if docups :
[3105]230                percent.display("%s...\n" % _("Rerouting printers to CUPS"))
[3073]231                for printer in printers :
[3105]232                    self.deroutePrinterFromPyKota(printer)
[3073]233                    percent.oneMore()
[2657]234        else :
[3413]235            if options["groups"] :
[2768]236                printersgroups = self.storage.getMatchingPrinters(options["groups"])
237                if not printersgroups :
238                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
[3413]239            else :
[2768]240                printersgroups = []
[3413]241
[2768]242            if options["charge"] :
243                try :
244                    charges = [float(part) for part in options["charge"].split(',', 1)]
[3413]245                except ValueError :
[2768]246                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
[3413]247                else :
[2768]248                    if len(charges) > 2 :
249                        charges = charges[:2]
250                    if len(charges) != 2 :
251                        charges = [charges[0], None]
252                    (perpage, perjob) = charges
[3413]253            else :
[2768]254                charges = perpage = perjob = None
[3413]255
256            if options["maxjobsize"] :
[2768]257                try :
258                    maxjobsize = int(options["maxjobsize"])
259                    if maxjobsize < 0 :
260                        raise ValueError
[3413]261                except ValueError :
[2768]262                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
[3413]263            else :
[2768]264                maxjobsize = None
[3413]265
[2768]266            description = options["description"]
267            if description :
268                description = description.strip()
[3413]269
270            nopassthrough = options["nopassthrough"]
[2768]271            passthrough = options["passthrough"]
272            remove = options["remove"]
273            skipexisting = options["skipexisting"]
[2770]274            self.storage.beginTransaction()
275            try :
[3413]276                if options["add"] :
[2783]277                    percent.display("%s...\n" % _("Creation"))
278                    percent.setSize(len(names))
[2782]279                    for pname in names :
[2770]280                        if self.isValidName(pname) :
281                            printer = StoragePrinter(self.storage, pname)
[2829]282                            self.modifyPrinter(printer, charges, perpage, perjob, \
[2770]283                                           description, passthrough, \
284                                           nopassthrough, maxjobsize)
[3413]285                            oldprinter = self.storage.addPrinter(printer)
286
[3073]287                            if docups :
[3105]288                                 self.reroutePrinterThroughPyKota(printer)
[3413]289
[2770]290                            if oldprinter is not None :
291                                if skipexisting :
292                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
[3413]293                                else :
[2770]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)
[3413]299                                    oldprinter.save()
[2770]300                                    self.managePrintersGroups(printersgroups, oldprinter, remove)
[3413]301                            elif printersgroups :
[2770]302                                self.managePrintersGroups(printersgroups, \
303                                                          self.storage.getPrinter(pname), \
304                                                          remove)
[3413]305                        else :
[2770]306                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
[2782]307                        percent.oneMore()
[3413]308                else :
[2783]309                    percent.display("\n%s...\n" % _("Modification"))
[3413]310                    for printer in printers :
[2770]311                        self.modifyPrinter(printer, charges, perpage, perjob, \
312                                           description, passthrough, \
313                                           nopassthrough, maxjobsize)
[3413]314                        printer.save()
[2770]315                        self.managePrintersGroups(printersgroups, printer, remove)
[3105]316                        if docups :
317                            self.reroutePrinterThroughPyKota(printer)
[2782]318                        percent.oneMore()
[3413]319            except :
[2770]320                self.storage.rollbackTransaction()
321                raise
[3413]322            else :
[2770]323                self.storage.commitTransaction()
[3413]324
[2783]325        if not options["list"] :
[2782]326            percent.done()
[3413]327
328if __name__ == "__main__" :
[1330]329    retcode = 0
330    try :
[3073]331        short_options = "hvaCc:D:dg:lrsnpm:"
332        long_options = ["help", "version", "add", "cups", "charge=", "description=", \
[2465]333                        "delete", "groups=", "list", "remove", \
334                        "skipexisting", "passthrough", "nopassthrough", \
335                        "maxjobsize="]
[3413]336
[1330]337        # Initializes the command line tool
338        manager = PKPrinters(doc=__doc__)
[2210]339        manager.deferredInit()
[3413]340
[1330]341        # parse and checks the command line
342        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
[3413]343
[1330]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"]
[3073]348        options["cups"] = options["C"] or options["cups"]
[1330]349        options["charge"] = options["c"] or options["charge"]
[1582]350        options["description"] = options["D"] or options["description"]
[3413]351        options["delete"] = options["d"] or options["delete"]
[1330]352        options["groups"] = options["g"] or options["groups"]
[1437]353        options["list"] = options["l"] or options["list"]
[1332]354        options["remove"] = options["r"] or options["remove"]
[1453]355        options["skipexisting"] = options["s"] or options["skipexisting"]
[2465]356        options["maxjobsize"] = options["m"] or options["maxjobsize"]
357        options["passthrough"] = options["p"] or options["passthrough"]
358        options["nopassthrough"] = options["n"] or options["nopassthrough"]
[3413]359
[1330]360        if options["help"] :
361            manager.display_usage_and_quit()
362        elif options["version"] :
363            manager.display_version_and_quit()
[1582]364        elif (options["delete"] and (options["add"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
[1453]365           or (options["skipexisting"] and not options["add"]) \
[2465]366           or (options["list"] and (options["add"] or options["delete"] or options["groups"] or options["charge"] or options["remove"] or options["description"])) \
[2768]367           or (options["passthrough"] and options["nopassthrough"]) \
368           or (options["remove"] and options["add"]) :
[2512]369            raise PyKotaCommandLineError, _("incompatible options, see help.")
[2768]370        elif options["remove"] and not options["groups"] :
[2512]371            raise PyKotaCommandLineError, _("You have to pass printer groups names on the command line")
[2768]372        elif (not args) and (options["add"] or options["delete"]) :
[2512]373            raise PyKotaCommandLineError, _("You have to pass printer names on the command line")
[1330]374        else :
375            retcode = manager.main(args, options)
[3413]376    except KeyboardInterrupt :
[3294]377        logerr("\nInterrupted with Ctrl+C !\n")
[2609]378        retcode = -3
[3413]379    except PyKotaCommandLineError, msg :
[3294]380        logerr("%s : %s\n" % (sys.argv[0], msg))
[2609]381        retcode = -2
[3413]382    except SystemExit :
[1526]383        pass
[1517]384    except :
385        try :
386            manager.crashed("pkprinters failed")
[3413]387        except :
[1546]388            crashed("pkprinters failed")
[1330]389        retcode = -1
390
391    try :
392        manager.storage.close()
[3413]393    except (TypeError, NameError, AttributeError) :
[1330]394        pass
[3413]395
396    sys.exit(retcode)
Note: See TracBrowser for help on using the browser.