root / pykota / trunk / bin / pkprinters @ 3423

Revision 3423, 16.9 kB (checked in by jerome, 16 years ago)

Ensure no incompatible command line options are 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: 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
28import pykota.appinit
29from pykota.utils import run
30from pykota.commandline import PyKotaOptionParser
31from pykota.errors import PyKotaCommandLineError
32from pykota.tool import Percent, PyKotaTool
33from pykota.storage import StoragePrinter
34
35from pkipplib import pkipplib
36
37class PKPrinters(PyKotaTool) :
38    """A class for a printers manager."""
39    def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) :
40        if charges :
41            printer.setPrices(perpage, perjob)
42        if description is not None :        # NB : "" is allowed !
43            printer.setDescription(description)
44        if nopassthrough :
45            printer.setPassThrough(False)
46        if passthrough :
47            printer.setPassThrough(True)
48        if maxjobsize is not None :
49            printer.setMaxJobSize(maxjobsize)
50
51    def managePrintersGroups(self, pgroups, printer, remove) :
52        """Manage printer group membership."""
53        for pgroup in pgroups :
54            if remove :
55                pgroup.delPrinterFromGroup(printer)
56            else :
57                pgroup.addPrinterToGroup(printer)
58
59    def getPrinterDeviceURI(self, printername) :
60        """Returns the Device URI attribute for a particular printer."""
61        if not printername :
62            return ""
63        cups = pkipplib.CUPS()
64        req = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
65        req.operation["printer-uri"] = ("uri", cups.identifierToURI("printers", printername))
66        req.operation["requested-attributes"] = ("keyword", "device-uri")
67        try :
68            return cups.doRequest(req).printer["device-uri"][0][1]
69        except (AttributeError, IndexError, KeyError) :
70            self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn")
71            return ""
72
73    def isPrinterCaptured(self, printername=None, deviceuri=None) :
74        """Returns True if the printer is already redirected through PyKota's backend, else False."""
75        if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 :
76            return True
77        else :
78            return False
79
80    def reroutePrinterThroughPyKota(self, printer) :
81        """Reroutes a CUPS printer through PyKota."""
82        uri = self.getPrinterDeviceURI(printer.Name)
83        if uri and (not self.isPrinterCaptured(deviceuri=uri)) :
84             newuri = "cupspykota://%s" % uri
85             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
86             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
87
88    def deroutePrinterFromPyKota(self, printer) :
89        """Deroutes a PyKota printer through CUPS only."""
90        uri = self.getPrinterDeviceURI(printer.Name)
91        if uri and self.isPrinterCaptured(deviceuri=uri) :
92             newuri = uri.replace("cupspykota:", "")
93             if newuri.startswith("//") :
94                 newuri = newuri[2:]
95             os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri))
96             self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri))
97
98    def main(self, names, options) :
99        """Manage printers."""
100        islist = (options.action == "list")
101        isadd = (options.action == "add")
102        isdelete = (options.action == "delete")
103
104        if not islist :
105            self.adminOnly()
106
107        if not names :
108            if isdelete or isadd :
109                raise PyKotaCommandLineError, _("You must specify printers names on the command line.")
110            names = [u"*"]
111
112        if options.remove and not options.groups :
113            raise PyKotaCommandLineError, _("You must specify printers groups names on the command line")
114        elif (((islist or isdelete) and (options.charge  \
115                                        or options.groups \
116                                        or options.remove \
117                                        or options.description \
118                                        or options.skipexisting \
119                                        or options.passthrough \
120                                        or options.nopassthrough \
121                                        or options.maxjobsize))) \
122              or (options.cups and options.list) :
123            raise PyKotaCommandLineError, _("Incompatible command line options. Please look at the online help or manual page.")
124
125        if not islist :
126            percent = Percent(self)
127
128        if not isadd :
129            if not islist :
130                percent.display("%s..." % _("Extracting datas"))
131            printers = self.storage.getMatchingPrinters(",".join(names))
132            if not printers :
133                if not islist :
134                    percent.display("\n")
135                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
136            if not islist :
137                percent.setSize(len(printers))
138
139        if islist :
140            for printer in printers :
141                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
142                self.display("%s [%s] (%s + #*%s)\n" % \
143                                     (printer.Name,
144                                      printer.Description,
145                                      printer.PricePerJob,
146                                      printer.PricePerPage))
147                self.display("    %s\n" % \
148                                     (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF"))))
149                self.display("    %s\n" % \
150                                     (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited"))))
151                self.display("    %s\n" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO"))))
152                if parents :
153                    self.display("    %s %s\n" % (_("in"), parents))
154                self.display("\n")
155        elif isdelete :
156            percent.display("\n%s..." % _("Deletion"))
157            self.storage.deleteManyPrinters(printers)
158            percent.display("\n")
159            if options.cups :
160                percent.display("%s...\n" % _("Rerouting printers to CUPS"))
161                for printer in printers :
162                    self.deroutePrinterFromPyKota(printer)
163                    percent.oneMore()
164        else :
165            if options.groups :
166                printersgroups = self.storage.getMatchingPrinters(options.groups)
167                if not printersgroups :
168                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options.groups.split(','))
169            else :
170                printersgroups = []
171
172            if options.charge :
173                try :
174                    charges = [float(part) for part in options.charge.split(',', 1)]
175                except ValueError :
176                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options.charge
177                else :
178                    if len(charges) > 2 :
179                        charges = charges[:2]
180                    if len(charges) != 2 :
181                        charges = [charges[0], None]
182                    (perpage, perjob) = charges
183            else :
184                charges = perpage = perjob = None
185
186            if options.maxjobsize :
187                try :
188                    maxjobsize = int(options.maxjobsize)
189                    if maxjobsize < 0 :
190                        raise ValueError
191                except ValueError :
192                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options.maxjobsize
193            else :
194                maxjobsize = None
195
196            description = options.description
197            if description :
198                description = description.strip()
199
200            self.storage.beginTransaction()
201            try :
202                if isadd :
203                    percent.display("%s...\n" % _("Creation"))
204                    percent.setSize(len(names))
205                    for pname in names :
206                        if self.isValidName(pname) :
207                            printer = StoragePrinter(self.storage, pname)
208                            self.modifyPrinter(printer,
209                                               charges,
210                                               perpage,
211                                               perjob,
212                                               description,
213                                               options.passthrough,
214                                               options.nopassthrough,
215                                               maxjobsize)
216                            oldprinter = self.storage.addPrinter(printer)
217
218                            if options.cups :
219                                 self.reroutePrinterThroughPyKota(printer)
220
221                            if oldprinter is not None :
222                                if options.skipexisting :
223                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
224                                else :
225                                    self.logdebug(_("Printer %s already exists, will be modified.") % pname)
226                                    self.modifyPrinter(oldprinter,
227                                                       charges,
228                                                       perpage,
229                                                       perjob,
230                                                       description,
231                                                       options.passthrough,
232                                                       options.nopassthrough,
233                                                       maxjobsize)
234                                    oldprinter.save()
235                                    self.managePrintersGroups(printersgroups,
236                                                              oldprinter,
237                                                              options.remove)
238                            else :
239                                self.managePrintersGroups(printersgroups, \
240                                                          self.storage.getPrinter(pname), \
241                                                          options.remove)
242                        else :
243                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
244                        percent.oneMore()
245                else :
246                    percent.display("\n%s...\n" % _("Modification"))
247                    for printer in printers :
248                        self.modifyPrinter(printer,
249                                           charges,
250                                           perpage,
251                                           perjob,
252                                           description,
253                                           options.passthrough,
254                                           options.nopassthrough,
255                                           maxjobsize)
256                        printer.save()
257                        self.managePrintersGroups(printersgroups,
258                                                  printer,
259                                                  options.remove)
260                        if options.cups :
261                            self.reroutePrinterThroughPyKota(printer)
262                        percent.oneMore()
263            except :
264                self.storage.rollbackTransaction()
265                raise
266            else :
267                self.storage.commitTransaction()
268
269        if not islist :
270            percent.done()
271
272if __name__ == "__main__" :
273    parser = PyKotaOptionParser(description=_("Manages PyKota printers."),
274                                usage="pkprinters [options] printer1 printer2 ... printerN")
275    parser.add_option("-a", "--add",
276                            action="store_const",
277                            const="add",
278                            dest="action",
279                            help=_("Add new, or modify existing, printers."))
280    parser.add_option("-c", "--charge",
281                            dest="charge",
282                            help=_("Set the cost per page, and optionally per job, for printing to the specified printers. If both are to be set, separate them with a comma. Floating point and negative values are allowed."))
283    parser.add_option("-C", "--cups",
284                            action="store_true",
285                            dest="cups",
286                            help=_("Tell CUPS to either start or stop managing the specified printers with PyKota."))
287    parser.add_option("-d", "--delete",
288                            action="store_const",
289                            const="delete",
290                            dest="action",
291                            help=_("Delete the specified printers. Also purge the print quota entries and printing history matching the specified printers."))
292    parser.add_option("-D", "--description",
293                            dest="description",
294                            help=_("Set a textual description for the specified printers."))
295    parser.add_option("-g", "--groups",
296                            dest="groups",
297                            help=_("If the --remove option is not used, the default action is to add the specified printers to the specified printers groups. Otherwise they are removed from these groups. The specified printers groups must already exist, and should be created beforehand just like normal printers with this very command."))
298    parser.add_option("-l", "--list",
299                            action="store_const",
300                            const="list",
301                            dest="action",
302                            help=_("Display detailed informations about the specified printers."))
303    parser.add_option("-m", "--maxjobsize",
304                            dest="maxjobsize",
305                            help=_("Set the maximum job size in pages allowed on the specified printers."))
306    parser.add_option("-n", "--nopassthrough",
307                            action="store_true",
308                            dest="nopassthrough",
309                            help=_("Deactivate passthrough mode for the specified printers. This is the normal mode of operations, in which print jobs are accounted for, and are checked against printing quotas and available credits."))
310    parser.add_option("-p", "--passthrough",
311                            action="store_true",
312                            dest="passthrough",
313                            help=_("Activate passthrough mode for the specified printers. In this mode, jobs sent to these printers are not accounted for. This can be useful for exams during which no user should be charged for his printouts."))
314    parser.add_option("-r", "--remove",
315                            action="store_true",
316                            dest="remove",
317                            help=_("When combined with the --groups option, remove printers from the specified printers groups."))
318    parser.add_option("-s", "--skipexisting",
319                            action="store_true",
320                            dest="skipexisting",
321                            help=_("If --add is used, ensure that existing printers won't be modified."))
322
323    parser.add_example('--add --cups -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200',
324                       _("Would create three printers named 'hp2100', and 'hp2200' in PyKota's database, while telling CUPS to route all print jobs through PyKota for these printers. Each of them would have 'HP Printer' as its description. Printing to any of them would cost 0.05 credit per page, plus 0.1 credit for each job."))
325    parser.add_example('--delete "*"',
326                       _("Would delete all existing printers and matching print quota entries and printing history from PyKota's database. USE WITH CARE."))
327    parser.add_example('--groups Laser,HP "hp*"',
328                       _("Would add all printers which name begins with 'hp' to the 'Laser' and 'HP' printers groups, which must already exist."))
329    parser.add_example("--groups Lexmark --remove hp2200",
330                       _("Would remove printer 'hp2200' from the 'Lexmark' printers group."))
331    run(parser, PKPrinters)
Note: See TracBrowser for help on using the browser.