root / pykota / trunk / bin / pkprinters @ 3421

Revision 3421, 16.1 kB (checked in by jerome, 16 years ago)

Now correctly catches the exception caused by virtual printers.

  • 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 not islist :
113            percent = Percent(self)
114
115        if not isadd :
116            if not islist :
117                percent.display("%s..." % _("Extracting datas"))
118            printers = self.storage.getMatchingPrinters(",".join(names))
119            if not printers :
120                if not islist :
121                    percent.display("\n")
122                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
123            if not islist :
124                percent.setSize(len(printers))
125
126        if islist :
127            for printer in printers :
128                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
129                self.display("%s [%s] (%s + #*%s)\n" % \
130                                     (printer.Name,
131                                      printer.Description,
132                                      printer.PricePerJob,
133                                      printer.PricePerPage))
134                self.display("    %s\n" % \
135                                     (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF"))))
136                self.display("    %s\n" % \
137                                     (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited"))))
138                self.display("    %s\n" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO"))))
139                if parents :
140                    self.display("    %s %s\n" % (_("in"), parents))
141                self.display("\n")
142        elif isdelete :
143            percent.display("\n%s..." % _("Deletion"))
144            self.storage.deleteManyPrinters(printers)
145            percent.display("\n")
146            if options.cups :
147                percent.display("%s...\n" % _("Rerouting printers to CUPS"))
148                for printer in printers :
149                    self.deroutePrinterFromPyKota(printer)
150                    percent.oneMore()
151        else :
152            if options.groups :
153                printersgroups = self.storage.getMatchingPrinters(options.groups)
154                if not printersgroups :
155                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options.groups.split(','))
156            else :
157                printersgroups = []
158
159            if options.charge :
160                try :
161                    charges = [float(part) for part in options.charge.split(',', 1)]
162                except ValueError :
163                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options.charge
164                else :
165                    if len(charges) > 2 :
166                        charges = charges[:2]
167                    if len(charges) != 2 :
168                        charges = [charges[0], None]
169                    (perpage, perjob) = charges
170            else :
171                charges = perpage = perjob = None
172
173            if options.maxjobsize :
174                try :
175                    maxjobsize = int(options.maxjobsize)
176                    if maxjobsize < 0 :
177                        raise ValueError
178                except ValueError :
179                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options.maxjobsize
180            else :
181                maxjobsize = None
182
183            description = options.description
184            if description :
185                description = description.strip()
186
187            self.storage.beginTransaction()
188            try :
189                if isadd :
190                    percent.display("%s...\n" % _("Creation"))
191                    percent.setSize(len(names))
192                    for pname in names :
193                        if self.isValidName(pname) :
194                            printer = StoragePrinter(self.storage, pname)
195                            self.modifyPrinter(printer,
196                                               charges,
197                                               perpage,
198                                               perjob,
199                                               description,
200                                               options.passthrough,
201                                               options.nopassthrough,
202                                               maxjobsize)
203                            oldprinter = self.storage.addPrinter(printer)
204
205                            if options.cups :
206                                 self.reroutePrinterThroughPyKota(printer)
207
208                            if oldprinter is not None :
209                                if options.skipexisting :
210                                    self.logdebug(_("Printer %s already exists, skipping.") % pname)
211                                else :
212                                    self.logdebug(_("Printer %s already exists, will be modified.") % pname)
213                                    self.modifyPrinter(oldprinter,
214                                                       charges,
215                                                       perpage,
216                                                       perjob,
217                                                       description,
218                                                       options.passthrough,
219                                                       options.nopassthrough,
220                                                       maxjobsize)
221                                    oldprinter.save()
222                                    self.managePrintersGroups(printersgroups,
223                                                              oldprinter,
224                                                              options.remove)
225                            else :
226                                self.managePrintersGroups(printersgroups, \
227                                                          self.storage.getPrinter(pname), \
228                                                          options.remove)
229                        else :
230                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
231                        percent.oneMore()
232                else :
233                    percent.display("\n%s...\n" % _("Modification"))
234                    for printer in printers :
235                        self.modifyPrinter(printer,
236                                           charges,
237                                           perpage,
238                                           perjob,
239                                           description,
240                                           options.passthrough,
241                                           options.nopassthrough,
242                                           maxjobsize)
243                        printer.save()
244                        self.managePrintersGroups(printersgroups,
245                                                  printer,
246                                                  options.remove)
247                        if options.cups :
248                            self.reroutePrinterThroughPyKota(printer)
249                        percent.oneMore()
250            except :
251                self.storage.rollbackTransaction()
252                raise
253            else :
254                self.storage.commitTransaction()
255
256        if not islist :
257            percent.done()
258
259if __name__ == "__main__" :
260    parser = PyKotaOptionParser(description=_("Manages PyKota printers."),
261                                usage="pkprinters [options] printer1 printer2 ... printerN")
262    parser.add_option("-a", "--add",
263                            action="store_const",
264                            const="add",
265                            dest="action",
266                            help=_("Add new, or modify existing, printers."))
267    parser.add_option("-c", "--charge",
268                            dest="charge",
269                            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."))
270    parser.add_option("-C", "--cups",
271                            action="store_true",
272                            dest="cups",
273                            help=_("Tell CUPS to either start or stop managing the specified printers with PyKota."))
274    parser.add_option("-d", "--delete",
275                            action="store_const",
276                            const="delete",
277                            dest="action",
278                            help=_("Delete the specified printers. Also purge the print quota entries and printing history matching the specified printers."))
279    parser.add_option("-D", "--description",
280                            dest="description",
281                            help=_("Set a textual description for the specified printers."))
282    parser.add_option("-g", "--groups",
283                            dest="groups",
284                            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."))
285    parser.add_option("-l", "--list",
286                            action="store_const",
287                            const="list",
288                            dest="action",
289                            help=_("Display detailed informations about the specified printers."))
290    parser.add_option("-m", "--maxjobsize",
291                            dest="maxjobsize",
292                            help=_("Set the maximum job size in pages allowed on the specified printers."))
293    parser.add_option("-n", "--nopassthrough",
294                            action="store_true",
295                            dest="nopassthrough",
296                            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."))
297    parser.add_option("-p", "--passthrough",
298                            action="store_true",
299                            dest="passthrough",
300                            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."))
301    parser.add_option("-r", "--remove",
302                            action="store_true",
303                            dest="remove",
304                            help=_("When combined with the --groups option, remove printers from the specified printers groups."))
305    parser.add_option("-s", "--skipexisting",
306                            action="store_true",
307                            dest="skipexisting",
308                            help=_("If --add is used, ensure that existing printers won't be modified."))
309
310    parser.add_example('--add --cups -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200',
311                       _("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."))
312    parser.add_example('--delete "*"',
313                       _("Would delete all existing printers and matching print quota entries and printing history from PyKota's database. USE WITH CARE."))
314    parser.add_example('--groups Laser,HP "hp*"',
315                       _("Would add all printers which name begins with 'hp' to the 'Laser' and 'HP' printers groups, which must already exist."))
316    parser.add_example("--groups Lexmark --remove hp2200",
317                       _("Would remove printer 'hp2200' from the 'Lexmark' printers group."))
318    run(parser, PKPrinters)
Note: See TracBrowser for help on using the browser.