root / pykota / trunk / bin / pkprinters @ 3425

Revision 3425, 16.9 kB (checked in by jerome, 15 years ago)

Cosmetic changes.

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