root / pykota / trunk / bin / edpykota @ 1156

Revision 1156, 25.8 kB (checked in by jalet, 21 years ago)

Multiple printer names or wildcards can be passed on the command line
separated with commas.
Beta phase.

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