root / pykota / trunk / bin / pkprinters @ 3481

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

Changed copyright years.
Copyright years are now dynamic when displayed by a command line tool.

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