root / pykota / trunk / bin / edpykota @ 1133

Revision 1133, 25.4 kB (checked in by jalet, 21 years ago)

Several optimizations, especially with LDAP backend

  • 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
3# PyKota Print Quota Editor
4#
5# PyKota - Print Quotas for CUPS and LPRng
6#
7# (c) 2003 Jerome Alet <alet@librelogiciel.com>
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21#
22# $Id$
23#
24# $Log$
25# Revision 1.58  2003/10/03 12:27:01  jalet
26# Several optimizations, especially with LDAP backend
27#
28# Revision 1.57  2003/08/20 16:01:19  jalet
29# Comment added.
30#
31# Revision 1.56  2003/07/29 20:55:17  jalet
32# 1.14 is out !
33#
34# Revision 1.55  2003/07/28 09:11:12  jalet
35# PyKota now tries to add its attributes intelligently in existing LDAP
36# directories.
37#
38# Revision 1.54  2003/07/21 06:32:42  jalet
39# Prevents email messages to be sent at modification/creation time for
40# a user/group quota
41#
42# Revision 1.53  2003/07/09 06:03:41  jalet
43# Fixed typo when using edpykota --prototype
44#
45# Revision 1.52  2003/07/07 12:11:13  jalet
46# Small fix
47#
48# Revision 1.51  2003/07/07 11:55:50  jalet
49# Small fix
50#
51# Revision 1.50  2003/07/05 12:33:53  jalet
52# More on previous fix.
53#
54# Revision 1.49  2003/07/05 12:32:07  jalet
55# Ensure that the user don't pass more than two prices for a printer.
56#
57# Revision 1.48  2003/06/25 19:52:30  jalet
58# Should be ready for testing :-)
59#
60# Revision 1.47  2003/06/25 14:10:01  jalet
61# Hey, it may work (edpykota --reset excepted) !
62#
63# Revision 1.46  2003/06/16 11:59:09  jalet
64# More work on LDAP
65#
66# Revision 1.45  2003/06/11 19:32:00  jalet
67# Severe bug wrt account balance setting should be corrected.
68#
69# Revision 1.44  2003/04/29 22:03:38  jalet
70# Better error handling.
71#
72# Revision 1.43  2003/04/23 22:13:56  jalet
73# Preliminary support for LPRng added BUT STILL UNTESTED.
74#
75# Revision 1.42  2003/04/17 13:38:47  jalet
76# Docstring corrected for better manual page
77#
78# Revision 1.41  2003/04/16 12:35:49  jalet
79# Groups quota work now !
80#
81# Revision 1.40  2003/04/16 08:22:09  jalet
82# More strict error detection.
83# Minor code rewrite to avoid some repetitive tests.
84#
85# Revision 1.39  2003/04/16 08:01:53  jalet
86# edpykota --charge command line option works now.
87#
88# Revision 1.38  2003/04/15 22:02:43  jalet
89# More complete docstring
90#
91# Revision 1.37  2003/04/15 21:58:33  jalet
92# edpykota now accepts a --delete option.
93# Preparation to allow edpykota to accept much more command line options
94# (WARNING : docstring is OK, but code isn't !)
95#
96# Revision 1.36  2003/04/15 13:55:28  jalet
97# Options --limitby and --balance added to edpykota
98#
99# Revision 1.35  2003/04/15 13:06:39  jalet
100# Allow to add a printer without any user
101#
102# Revision 1.34  2003/04/11 16:51:11  jalet
103# Bug fix for edpykota --add with users who already had a quota on the printer.
104#
105# Revision 1.33  2003/04/10 21:47:20  jalet
106# Job history added. Upgrade script neutralized for now !
107#
108# Revision 1.32  2003/04/08 21:31:39  jalet
109# (anything or 0) = anything !!! Go back to school Jerome !
110#
111# Revision 1.31  2003/04/08 21:13:44  jalet
112# Prepare --groups option to work.
113#
114# Revision 1.30  2003/04/08 21:10:18  jalet
115# Checks --groups option presence instead of --users because --users is the default.
116#
117# Revision 1.29  2003/04/05 09:28:56  jalet
118# Unnecessary message was logged
119#
120# Revision 1.28  2003/03/29 13:45:26  jalet
121# GPL paragraphs were incorrectly (from memory) copied into the sources.
122# Two README files were added.
123# Upgrade script for PostgreSQL pre 1.01 schema was added.
124#
125# Revision 1.27  2003/03/10 00:23:04  jalet
126# Bad english
127#
128# Revision 1.26  2003/03/10 00:11:27  jalet
129# Cleaner example.
130#
131# Revision 1.25  2003/03/09 23:56:21  jalet
132# Option noquota added to do accounting only.
133#
134# Revision 1.24  2003/02/27 23:48:41  jalet
135# Correctly maps PyKota's log levels to syslog log levels
136#
137# Revision 1.23  2003/02/27 22:55:20  jalet
138# WARN log priority doesn't exist.
139#
140# Revision 1.22  2003/02/27 09:37:02  jalet
141# Wildcards seem to work now
142#
143# Revision 1.21  2003/02/27 09:04:46  jalet
144# user and group names can be passed as wildcards if the --add option
145# is not set. The default is to act on all users or groups.
146#
147# Revision 1.20  2003/02/10 12:07:30  jalet
148# Now repykota should output the recorded total page number for each printer too.
149#
150# Revision 1.19  2003/02/09 13:40:29  jalet
151# typo
152#
153# Revision 1.18  2003/02/09 12:56:53  jalet
154# Internationalization begins...
155#
156# Revision 1.17  2003/02/08 22:47:23  jalet
157# Option --reset can now be used without having to use soft and hard limits
158# on the command line.
159#
160# Revision 1.16  2003/02/08 22:39:46  jalet
161# --reset command line option added
162#
163# Revision 1.15  2003/02/08 22:20:01  jalet
164# Clarification on why we don't check with /etc/passwd to see if the user
165# name is valid or not.
166#
167# Revision 1.14  2003/02/08 22:18:15  jalet
168# Now checks user and group names for validity before adding them
169#
170# Revision 1.13  2003/02/08 22:09:02  jalet
171# Only printer was added the first time.
172#
173# Revision 1.12  2003/02/08 21:44:49  jalet
174# Python 2.1 string module doesn't define ascii_letters
175#
176# Revision 1.11  2003/02/08 09:42:44  jalet
177# Better handle wrong or bad command line arguments
178#
179# Revision 1.10  2003/02/08 09:39:20  jalet
180# typos
181#
182# Revision 1.9  2003/02/08 09:38:06  jalet
183# Badly placed test
184#
185# Revision 1.8  2003/02/07 22:53:57  jalet
186# Checks if printer name is valid before adding it
187#
188# Revision 1.7  2003/02/07 22:17:58  jalet
189# Incomplete test
190#
191# Revision 1.6  2003/02/07 22:13:13  jalet
192# Perhaps edpykota is now able to add printers !!! Oh, stupid me !
193#
194# Revision 1.5  2003/02/06 14:49:04  jalet
195# edpykota should be ok now
196#
197# Revision 1.4  2003/02/06 14:28:59  jalet
198# edpykota should be ok, minus some typos
199#
200# Revision 1.3  2003/02/06 10:47:21  jalet
201# Documentation string and command line options didn't match.
202#
203# Revision 1.2  2003/02/06 10:39:23  jalet
204# Preliminary edpykota work.
205#
206# Revision 1.1  2003/02/05 21:41:09  jalet
207# Skeletons added for all command line tools
208#
209#
210#
211
212import sys
213
214from pykota import version
215from pykota.tool import PyKotaTool, PyKotaToolError
216from pykota.config import PyKotaConfigError
217from pykota.storage import PyKotaStorageError
218
219__doc__ = """edpykota v%s (C) 2003 C@LL - Conseil Internet & Logiciels Libres
220A Print Quota editor for PyKota.
221
222command line usage :
223
224  edpykota [options] user1 user2 ... userN
225 
226  edpykota [options] group1 group2 ... groupN
227
228options :
229
230  -v | --version       Prints edpykota's version number then exits.
231  -h | --help          Prints this message then exits.
232 
233  -a | --add           Adds users and/or printers if they don't
234                       exist on the Quota Storage Server.
235                       
236  -d | --delete        Deletes users/groups from the quota storage.
237                       Printers are never deleted.
238                       
239  -c | --charge p[,j]  Sets the price per page and per job to charge
240                       for a particular printer. Job price is optional.
241                       If both are to be set, separate them with a comma.
242                       Floating point values are allowed.
243 
244  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
245                              listed, separated by commas. The groups
246                              must already exist in the Quota Storage.
247 
248  -u | --users         Edit users print quotas, this is the default.
249 
250  -P | --printer p     Edit quotas on printer p only. Actually p can
251                       use wildcards characters to select only
252                       some printers. The default value is *, meaning
253                       all printers.
254 
255  -g | --groups        Edit groups print quotas instead of users.
256                         
257  -p | --prototype u|g Uses user u or group g as a prototype to set
258                       print quotas
259                       
260  -n | --noquota       Doesn't set a quota but only does accounting.
261 
262  -r | --reset         Resets the printed page counter for the user
263                       or group to zero. The life time page counter
264                       is kept unchanged.
265                       
266  -l | --limitby l     Choose if the user/group is limited in printing                     
267                       by its account balance or by its page quota.
268                       The default value is 'quota'. Allowed values
269                       are 'quota' and 'balance'.
270                       
271  -b | --balance b     Sets the user's account balance to b.                     
272                       Account balance may be increase or decreased
273                       if b is prefixed with + or -.
274                       WARNING : when decreasing account balance,
275                       the total paid so far by the user is decreased
276                       too.
277                       Groups don't have a real balance, but the
278                       sum of their users' account balance.
279                       
280  -S | --softlimit sl  Sets the quota soft limit to sl pages.                       
281 
282  -H | --hardlimit hl  Sets the quota hard limit to hl pages.
283 
284  user1 through userN and group1 through groupN can use wildcards
285  if the --add option is not set.
286 
287examples :                             
288
289  $ edpykota -p jerome john paul george ringo
290 
291  This will set print quotas for the users john, paul, george and ringo
292  to the same values than user jerome. User jerome must exist.
293 
294  $ edpykota --printer lp -S 50 -H 60 jerome
295 
296  This will set jerome's print quota on the lp printer to a soft limit
297  of 50 pages, and a hard limit of 60 pages. If either user jerome or
298  printer lp doesn't exist on the Quota Storage Server then nothing is done.
299
300  $ edpykota --add --printer lp --ingroups coders,it -S 50 -H 60 jerome
301 
302  Same as above, but if either user jerome or printer lp doesn't exist
303  on the Quota Storage Server they are automatically added. Also
304  user jerome is put into the groups "coders" and "it" which must
305  already exist in the Quota Storage.
306  WARNING : the CUPS PPD file for this printer or the /etc/printcap
307            file (depending on your printing backend)  must still be
308            modified manually, as well as pykota's configuration file
309            for a new printer to be managed successfully.
310           
311  $ edpykota -g -S 500 -H 550 financial support           
312 
313  This will set print quota soft limit to 500 pages and hard limit
314  to 550 pages for groups financial and support on all printers.
315 
316  $ edpykota --reset jerome "jo*"
317 
318  This will reset jerome's page counter to zero on all printers, as
319  well as every user whose name begins with 'jo'.
320  Their life time page counter on each printer will be kept unchanged.
321 
322  $ edpykota --printer hpcolor --noquota jerome
323 
324  This will tell PyKota to not limit jerome when printing on the
325  hpcolor printer. All his jobs will be allowed on this printer, but
326  accounting of the pages he prints will still be kept.
327  Print Quotas for jerome on other printers are unchanged.
328 
329  $ edpykota --limitby balance jerome
330 
331  This will tell PyKota to limit jerome by his account's balance
332  when printing.
333 
334  $ edpykota --balance +10.0 jerome
335 
336  This will increase jerome's account balance by 10.0 (in your
337  own currency). You can decrease the account balance with a
338  dash prefix, and set it to a fixed amount with no prefix.
339 
340  $ edpykota --delete jerome rachel
341 
342  This will completely delete jerome and rachel from the Quota Storage
343  database. All their quotas and jobs will be deleted too.
344 
345  $ edpykota --printer lp --charge 0.1
346 
347  This will set the page price for printer lp to 0.1. Job price
348  will not be changed.
349
350This program is free software; you can redistribute it and/or modify
351it under the terms of the GNU General Public License as published by
352the Free Software Foundation; either version 2 of the License, or
353(at your option) any later version.
354
355This program is distributed in the hope that it will be useful,
356but WITHOUT ANY WARRANTY; without even the implied warranty of
357MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
358GNU General Public License for more details.
359
360You should have received a copy of the GNU General Public License
361along with this program; if not, write to the Free Software
362Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
363
364Please e-mail bugs to: %s""" % (version.__version__, version.__author__)
365       
366class EdPyKota(PyKotaTool) :       
367    """A class for edpykota."""
368    def main(self, names, options) :
369        """Edit user or group quotas."""
370       
371        suffix = (options["groups"] and "Group") or "User"       
372       
373        softlimit = hardlimit = None   
374        if not options["noquota"] :
375            if options["softlimit"] :
376                try :
377                    softlimit = int(options["softlimit"].strip())
378                except ValueError :   
379                    raise PyKotaToolError, _("Invalid softlimit value %s.") % options["softlimit"]
380            if options["hardlimit"] :
381                try :
382                    hardlimit = int(options["hardlimit"].strip())
383                except ValueError :   
384                    raise PyKotaToolError, _("Invalid hardlimit value %s.") % options["hardlimit"]
385            if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :       
386                # error, exchange them
387                self.logger.log_message(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
388                (softlimit, hardlimit) = (hardlimit, softlimit)
389           
390        balance = options["balance"]
391        if balance :
392            balance = balance.strip()
393            try :
394                balancevalue = float(balance)
395            except ValueError :   
396                raise PyKotaToolError, _("Invalid balance value %s" % options["balance"])
397           
398        if options["charge"] :
399            try :
400                charges = [float(part) for part in options["charge"].split(',', 1)]
401            except ValueError :   
402                raise PyKotaToolError, _("Invalid charge amount value %s" % options["charge"])
403            else :   
404                if len(charges) > 2 :
405                    charges = charges[:2]
406                if len(charges) != 2 :
407                    charges = [charges[0], None]
408                   
409        limitby = options["limitby"]
410        if limitby :
411            limitby = limitby.strip().lower()
412        if limitby and (limitby not in ('quota', 'balance')) :   
413            raise PyKotaToolError, _("Invalid limitby value %s" % options["limitby"])
414           
415        if options["ingroups"] :   
416            groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
417        else :   
418            groupnames = []
419           
420        if options["prototype"] :   
421            protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
422           
423        printeradded = 0
424        printers = self.storage.getMatchingPrinters(options["printer"])
425        if not printers :
426            pname = options["printer"]
427            if options["add"] and pname :
428                if self.isValidName(pname) :
429                    printers = [ self.storage.addPrinter(pname) ]
430                    if printers[0].Exists :
431                        printeradded = 1
432                    else :   
433                        raise PyKotaToolError, _("Impossible to add printer %s") % pname
434                else :   
435                    raise PyKotaToolError, _("Invalid printer name %s") % pname
436            else :
437                raise PyKotaToolError, _("There's no printer matching %s") % pname
438        if not names :   
439            if options["add"] and not printeradded :
440                raise PyKotaToolError, _("You have to pass user or group names on the command line")
441            else :   
442                names = [ "*" ] # all users
443               
444        changed = {} # tracks changes made at the user/group level
445        for printer in printers :
446            if options["charge"] :
447                (perpage, perjob) = charges
448                printer.setPrices(perpage, perjob)   
449               
450            if options["prototype"] :
451                if protoentry.Exists :
452                    protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
453                    if not protoquota.Exists :
454                        self.logger.log_message(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
455                        continue    # skip this printer
456                    else :   
457                        (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
458                else :       
459                    self.logger.log_message(_("Prototype object %s not found in Quota Storage.") % protoentry.Name)
460                   
461            if not options["noquota"] :   
462                if hardlimit is None :   
463                    hardlimit = softlimit
464                    if hardlimit is not None :
465                        self.logger.log_message(_("Undefined hard limit set to soft limit (%s) on printer %s.") % (str(hardlimit), printer.Name))
466                if softlimit is None :   
467                    softlimit = hardlimit
468                    if softlimit is not None :
469                        self.logger.log_message(_("Undefined soft limit set to hard limit (%s) on printer %s.") % (str(softlimit), printer.Name))
470                       
471            if options["add"] :   
472                allentries = []   
473                for name in names :
474                    entry = getattr(self.storage, "get%s" % suffix)(name)
475                    entrypquota = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer)
476                    allentries.append((entry, entrypquota))
477            else :   
478                allentries = getattr(self.storage, "getPrinter%ssAndQuotas" % suffix)(printer, names)
479               
480            for (entry, entrypquota) in allentries :
481                if not changed.has_key(entry.Name) :
482                    changed[entry.Name] = {}
483                    if not options["groups"] :
484                        changed[entry.Name]["ingroups"] = []
485                       
486                if not entry.Exists :       
487                    # not found
488                    if options["add"] :
489                        # In case we want to add something, it is crucial
490                        # that we DON'T check with the system accounts files
491                        # like /etc/passwd because users may be defined
492                        # only remotely
493                        if self.isValidName(entry.Name) :
494                            entry = getattr(self.storage, "add%s" % suffix)(entry)
495                        else :   
496                            if options["groups"] :
497                                self.logger.log_message(_("Invalid group name %s") % entry.Name)
498                            else :   
499                                self.logger.log_message(_("Invalid user name %s") % entry.Name)
500                               
501                if not entrypquota.Exists :
502                    # not found
503                    if options["add"] :
504                        entrypquota = getattr(self.storage, "add%sPQuota" % suffix)(entry, printer)
505                       
506                if not entrypquota.Exists :     
507                    self.logger.log_message(_("Quota not found for object %s on printer %s.") % (entry.Name, printer.Name))
508                else :   
509                    if options["delete"] :
510                        entry.delete()
511                    else :
512                        if options["noquota"] or options["prototype"] or ((softlimit is not None) and (hardlimit is not None)) :
513                            entrypquota.setLimits(softlimit, hardlimit)
514                        if limitby :
515                            if changed[entry.Name].get("limitby") is None :
516                                entry.setLimitBy(limitby)
517                                changed[entry.Name]["limitby"] = limitby
518                       
519                        if not options["groups"] :
520                            if options["reset"] :
521                                entrypquota.reset()
522                               
523                            if balance :
524                                if changed[entry.Name].get("balance") is None :
525                                    if balance.startswith("+") or balance.startswith("-") :
526                                        newbalance = float(entry.AccountBalance or 0.0) + balancevalue
527                                        newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
528                                        entry.setAccountBalance(newbalance, newlifetimepaid)
529                                    else :
530                                        diff = balancevalue - float(entry.AccountBalance or 0.0)
531                                        newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
532                                        entry.setAccountBalance(balancevalue, newlifetimepaid)
533                                    changed[entry.Name]["balance"] = balance
534                                   
535                            for groupname in groupnames :       
536                                # not executed if option --ingroups is not used
537                                if groupname not in changed[entry.Name]["ingroups"] :
538                                    group = self.storage.getGroup(groupname)
539                                    if group.Exists :
540                                        self.storage.addUserToGroup(entry, group)
541                                        changed[entry.Name]["ingroups"].append(groupname)
542                                    else :
543                                        self.logger.log_message(_("Group %s not found in the PyKota Storage.") % groupname)
544                                       
545                        # This line disabled to prevent sending of unwanted email                 
546                        # messages if quota is reached at creation/modification time.
547                        # The check will be done at print time anyway.
548                        # getattr(self, "warn%sPQuota" % suffix)(entrypquota)   
549                     
550if __name__ == "__main__" : 
551    retcode = 0
552    try :
553        defaults = { \
554                     "printer" : "*", \
555                   }
556        short_options = "vhdc:l:b:i:naugrp:P:S:H:"
557        long_options = ["help", "version", "charge=", "delete", "limitby=", "balance=", "ingroups=", "noquota", "add", "users", "groups", "reset", "prototype=", "printer=", "softlimit=", "hardlimit="]
558       
559        # Initializes the command line tool
560        editor = EdPyKota(doc=__doc__)
561       
562        # parse and checks the command line
563        (options, args) = editor.parseCommandline(sys.argv[1:], short_options, long_options)
564       
565        # sets long options
566        options["help"] = options["h"] or options["help"]
567        options["version"] = options["v"] or options["version"]
568        options["add"] = options["a"] or options["add"]
569        options["users"] = options["u"] or options["users"]
570        options["groups"] = options["g"] or options["groups"]
571        options["prototype"] = options["p"] or options["prototype"]
572        options["printer"] = options["P"] or options["printer"] or defaults["printer"]
573        options["softlimit"] = options["S"] or options["softlimit"]
574        options["hardlimit"] = options["H"] or options["hardlimit"] 
575        options["reset"] = options["r"] or options["reset"] 
576        options["noquota"] = options["n"] or options["noquota"]
577        options["limitby"] = options["l"] or options["limitby"]
578        options["balance"] = options["b"] or options["balance"] 
579        options["delete"] = options["d"] or options["delete"] 
580        options["ingroups"] = options["i"] or options["ingroups"]
581        options["charge"] = options["c"] or options["charge"]
582       
583        if options["help"] :
584            editor.display_usage_and_quit()
585        elif options["version"] :
586            editor.display_version_and_quit()
587        elif options["users"] and options["groups"] :   
588            raise PyKotaToolError, _("incompatible options, see help.")
589        elif (options["add"] or options["prototype"]) and options["delete"] :   
590            raise PyKotaToolError, _("incompatible options, see help.")
591        elif (options["softlimit"] or options["hardlimit"]) and options["prototype"] :   
592            raise PyKotaToolError, _("incompatible options, see help.")
593        elif options["noquota"] and (options["prototype"] or options["hardlimit"] or options["softlimit"]) :
594            raise PyKotaToolError, _("incompatible options, see help.")
595        elif options["groups"] and (options["balance"] or options["ingroups"]) :
596            raise PyKotaToolError, _("incompatible options, see help.")
597        else :
598            retcode = editor.main(args, options)
599    except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError), msg :           
600        sys.stderr.write("%s\n" % msg)
601        sys.stderr.flush()
602        retcode = -1
603
604    try :
605        editor.storage.close()
606    except (TypeError, NameError, AttributeError) :   
607        pass
608       
609    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.