root / pykota / trunk / bin / edpykota @ 1113

Revision 1113, 25.2 kB (checked in by jalet, 21 years ago)

1.14 is out !

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