root / pykota / trunk / bin / edpykota @ 1148

Revision 1144, 25.5 kB (checked in by jalet, 21 years ago)

Character encoding added to please latest version of Python

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