root / pykota / trunk / bin / edpykota @ 2344

Revision 2344, 26.9 kB (checked in by jerome, 19 years ago)

Moved the GPL blurb into a single location.
Now uses named parameters in commands' help.

  • 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, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import sys
28import os
29import pwd
30import grp
31from pykota.tool import PyKotaTool, PyKotaToolError, crashed, N_
32from pykota.config import PyKotaConfigError
33from pykota.storage import PyKotaStorageError
34
35__doc__ = N_("""edpykota v%(__version__)s (c) %(__years__)s %(__author__)s
36
37A Print Quota editor for PyKota.
38
39command line usage :
40
41  edpykota [options] user1 user2 ... userN
42 
43  edpykota [options] group1 group2 ... groupN
44
45options :
46
47  -v | --version       Prints edpykota's version number then exits.
48  -h | --help          Prints this message then exits.
49 
50  -a | --add           Adds users and/or printers if they don't
51                       exist on the Quota Storage Server.
52                       
53  -d | --delete        Deletes users/groups from the quota storage.
54                       Printers are never deleted.
55                       
56  -c | --charge p[,j]  Sets the price per page and per job to charge
57                       for a particular printer. Job price is optional.
58                       If both are to be set, separate them with a comma.
59                       Floating point values are allowed.
60                       
61  -o | --overcharge f  Sets the overcharging factor applied to the user
62                       when computing the cost of a print job. Positive or
63                       negative floating point values are allowed,
64                       this allows you to do some really creative
65                       things like giving money to an user whenever
66                       he prints. The number of pages in a print job
67                       is not modified by this coefficient, only the
68                       cost of the job for a particular user.
69                       Only users have a coefficient.
70 
71  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
72                              listed, separated by commas. The groups
73                              must already exist in the Quota Storage.
74 
75  -u | --users         Edit users print quotas, this is the default.
76 
77  -P | --printer p     Edit quotas on printer p only. Actually p can
78                       use wildcards characters to select only
79                       some printers. The default value is *, meaning
80                       all printers.
81                       You can specify several names or wildcards,
82                       by separating them with commas.
83 
84  -G | --pgroups pg1[,pg2...] Adds the printer(s) to the printer groups
85                       pg1, pg2, etc... which must already exist.
86                       A printer group is just like a normal printer,
87                       only that it is usually unknown from the printing
88                       system. Create printer groups exactly the same
89                       way that you create printers, then add other
90                       printers to them with this option.
91                       Accounting is done on a printer and on all
92                       the printer groups it belongs to, quota checking
93                       is done on a printer and on all the printer groups
94                       it belongs to.
95 
96  -g | --groups        Edit users groups print quotas instead of users.
97                         
98  -p | --prototype u|g Uses user u or group g as a prototype to set
99                       print quotas
100                       
101  -n | --noquota       Doesn't set a quota but only does accounting.
102 
103  -r | --reset         Resets the actual page counter for the user
104                       or group to zero on the specified printers.
105                       The life time page counter is kept unchanged.
106                       
107  -R | --hardreset     Resets the actual and life time page counters
108                       for the user or group to zero on the specified
109                       printers. This is a shortcut for '--used 0'.
110                       
111  -l | --limitby l     Choose if the user/group is limited in printing                     
112                       by its account balance or by its page quota.
113                       The default value is 'quota'. Allowed values
114                       are 'quota' 'balance' 'quota-then-balance' and
115                       'balance-then-quota'.
116                       WARNING : quota-then-balance and balance-then-quota
117                       are not yet implemented.
118                       
119  -b | --balance b     Sets the user's account balance to b.                     
120                       Account balance may be increase or decreased
121                       if b is prefixed with + or -.
122                       WARNING : when decreasing account balance,
123                       the total paid so far by the user is decreased
124                       too.
125                       Groups don't have a real balance, but the
126                       sum of their users' account balance.
127                       
128  -S | --softlimit sl  Sets the quota soft limit to sl pages.                       
129 
130  -H | --hardlimit hl  Sets the quota hard limit to hl pages.
131
132  -U | --used usage    Sets the pagecounters for the user to usage pages;
133                       useful for migrating users from a different system
134                       where they have already used some pages. Actual
135                       and Life Time page counters may be increased or decreased
136                       if usage is prefixed with + or -.
137                       WARNING : BOTH page counters are modified in all cases,
138                       so be careful.
139                       NB : if 'usage' equals '0', then the action taken is
140                       the same as if --hardreset was used.
141
142  user1 through userN and group1 through groupN can use wildcards
143  if the --add option is not set.
144 
145examples :                             
146
147  $ edpykota --add -p jerome john paul george ringo/ringo@example.com
148 
149  This will add users john, paul, george and ringo to the quota
150  database, and set their print quotas to the same values than user
151  jerome. User jerome must already exist.
152  User ringo's email address will also be set to 'ringo@example.com'
153 
154  $ edpykota --printer lp -S 50 -H 60 jerome
155 
156  This will set jerome's print quota on the lp printer to a soft limit
157  of 50 pages, and a hard limit of 60 pages. If either user jerome or
158  printer lp doesn't exist on the Quota Storage Server then nothing is done.
159
160  $ edpykota --add --printer lp --ingroups coders,it -S 50 -H 60 jerome
161 
162  Same as above, but if either user jerome or printer lp doesn't exist
163  on the Quota Storage Server they are automatically added. Also
164  user jerome is put into the groups "coders" and "it" which must
165  already exist in the Quota Storage.
166           
167  $ edpykota -g -S 500 -H 550 financial support           
168 
169  This will set print quota soft limit to 500 pages and hard limit
170  to 550 pages for groups financial and support on all printers.
171 
172  $ edpykota --reset jerome "jo*"
173 
174  This will reset jerome's page counter to zero on all printers, as
175  well as every user whose name begins with 'jo'.
176  Their life time page counter on each printer will be kept unchanged.
177  You can also reset the life time page counters by using the
178  --hardreset | -R command line option.
179 
180  $ edpykota --printer hpcolor --noquota jerome
181 
182  This will tell PyKota to not limit jerome when printing on the
183  hpcolor printer. All his jobs will be allowed on this printer, but
184  accounting of the pages he prints will still be kept.
185  Print Quotas for jerome on other printers are unchanged.
186 
187  $ edpykota --limitby balance jerome
188 
189  This will tell PyKota to limit jerome by his account's balance
190  when printing.
191 
192  $ edpykota --balance +10.0 jerome
193 
194  This will increase jerome's account balance by 10.0 (in your
195  own currency). You can decrease the account balance with a
196  dash prefix, and set it to a fixed amount with no prefix.
197 
198  $ edpykota --delete jerome rachel
199 
200  This will completely delete jerome and rachel from the Quota Storage
201  database. All their quotas and jobs will be deleted too.
202 
203  $ edpykota --printer lp --charge 0.1
204 
205  This will set the page price for printer lp to 0.1. Job price
206  will not be changed.
207 
208  $ edpykota --printer hplj1,hplj2 --pgroups Laser,HP
209 
210  This will put printers hplj1 and hplj2 in printers groups Laser and HP.
211  When printing either on hplj1 or hplj2, print quota will also be
212  checked and accounted for on virtual printers Laser and HP.
213 
214  $ edpykota --overcharge 2.5 poorstudent
215 
216  This will overcharge the poorstudent user by a factor of 2.5.
217 
218  $ edpykota --overcharge -1 jerome
219 
220  User jerome will actually earn money whenever he prints.
221 
222  $ edpykota --overcharge 0 boss
223 
224  User boss can print at will, it won't cost him anything because the
225  cost of each print job will be multiplied by zero before charging
226  his account.
227""") 
228       
229class EdPyKota(PyKotaTool) :       
230    """A class for edpykota."""
231    def main(self, names, options) :
232        """Edit user or group quotas."""
233        if not self.config.isAdmin :
234            raise PyKotaToolError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
235       
236        suffix = (options["groups"] and "Group") or "User"       
237       
238        softlimit = hardlimit = None
239
240        used = options["used"]
241        if used :
242            used = used.strip()
243            try :
244                int(used)
245            except ValueError :
246                raise PyKotaToolError, _("Invalid used value %s.") % used
247
248        if not options["noquota"] :
249            if options["softlimit"] :
250                try :
251                    softlimit = int(options["softlimit"].strip())
252                except ValueError :   
253                    raise PyKotaToolError, _("Invalid softlimit value %s.") % options["softlimit"]
254            if options["hardlimit"] :
255                try :
256                    hardlimit = int(options["hardlimit"].strip())
257                except ValueError :   
258                    raise PyKotaToolError, _("Invalid hardlimit value %s.") % options["hardlimit"]
259            if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :       
260                # error, exchange them
261                self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
262                (softlimit, hardlimit) = (hardlimit, softlimit)
263           
264        overcharge = options["overcharge"]
265        if overcharge :
266            try :
267                overcharge = float(overcharge.strip())
268            except (ValueError, AttributeError) :   
269                raise PyKotaToolError, _("Invalid overcharge value %s") % options["overcharge"]
270               
271        balance = options["balance"]
272        if balance :
273            balance = balance.strip()
274            try :
275                balancevalue = float(balance)
276            except ValueError :   
277                raise PyKotaToolError, _("Invalid balance value %s") % options["balance"]
278           
279        if options["charge"] :
280            try :
281                charges = [float(part) for part in options["charge"].split(',', 1)]
282            except ValueError :   
283                raise PyKotaToolError, _("Invalid charge amount value %s") % options["charge"]
284            else :   
285                if len(charges) > 2 :
286                    charges = charges[:2]
287                if len(charges) != 2 :
288                    charges = [charges[0], None]
289                   
290        limitby = options["limitby"]
291        if limitby :
292            limitby = limitby.strip().lower()
293        if limitby and (limitby not in ('quota', 'balance', 'quota-then-balance', 'balance-then-quota')) :   
294            raise PyKotaToolError, _("Invalid limitby value %s") % options["limitby"]
295           
296        if options["ingroups"] :   
297            groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
298        else :   
299            groupnames = []
300           
301        rejectunknown = self.config.getRejectUnknown()   
302        printeradded = 0
303        printers = self.storage.getMatchingPrinters(options["printer"])
304        if not printers :
305            pname = options["printer"]
306            if options["add"] and pname :
307                if self.isValidName(pname) :
308                    printers = [ self.storage.addPrinter(pname) ]
309                    if printers[0].Exists :
310                        printeradded = 1
311                    else :   
312                        raise PyKotaToolError, _("Impossible to add printer %s") % pname
313                else :   
314                    raise PyKotaToolError, _("Invalid printer name %s") % pname
315            else :
316                raise PyKotaToolError, _("There's no printer matching %s") % pname
317        if not names :   
318            if options["delete"] :   
319                raise PyKotaToolError, _("You have to pass user or group names on the command line")
320            else :
321                names = getattr(self.storage, "getAll%ssNames" % suffix)() # all users or groups
322               
323        printersgroups = []       
324        if options["pgroups"] :       
325            printersgroups = self.storage.getMatchingPrinters(options["pgroups"])
326           
327        if options["prototype"] :   
328            protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
329            if not protoentry.Exists :
330                raise PyKotaToolError, _("Prototype object %s not found in Quota Storage.") % protoentry.Name
331            else :   
332                limitby = protoentry.LimitBy
333                balancevalue = protoentry.AccountBalance
334                if balancevalue is not None :
335                    balance = str(abs(balancevalue))
336                else :   
337                    balance = None
338                overcharge = getattr(protoentry, "OverCharge", None)
339           
340        missingusers = {}
341        missinggroups = {}   
342        todelete = {}   
343        changed = {} # tracks changes made at the user/group level
344        for printer in printers :
345            for pgroup in printersgroups :
346                pgroup.addPrinterToGroup(printer)   
347               
348            if options["charge"] :
349                (perpage, perjob) = charges
350                printer.setPrices(perpage, perjob)   
351               
352            if options["prototype"] :
353                protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
354                if not protoquota.Exists :
355                    self.printInfo(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
356                else :   
357                    (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
358                   
359            if not options["noquota"] :   
360                if hardlimit is None :   
361                    hardlimit = softlimit
362                    if hardlimit is not None :
363                        self.printInfo(_("Undefined hard limit set to soft limit (%s) on printer %s.") % (str(hardlimit), printer.Name))
364                if softlimit is None :   
365                    softlimit = hardlimit
366                    if softlimit is not None :
367                        self.printInfo(_("Undefined soft limit set to hard limit (%s) on printer %s.") % (str(softlimit), printer.Name))
368                       
369            if options["add"] :   
370                allentries = []   
371                for name in names :
372                    email = ""
373                    if not options["groups"] :
374                        splitname = name.split('/', 1)     # username/email
375                        if len(splitname) == 1 :
376                            splitname.append("")
377                        (name, email) = splitname
378                        if email and (email.count('@') != 1) :
379                            self.printInfo(_("Invalid email address %s") % email)
380                            email = ""
381                    entry = getattr(self.storage, "get%s" % suffix)(name)
382                    if email and not options["groups"] :
383                        entry.Email = email
384                    entrypquota = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer)
385                    allentries.append((entry, entrypquota))
386            else :   
387                allentries = getattr(self.storage, "getPrinter%ssAndQuotas" % suffix)(printer, names)
388               
389            # TODO : do this only once !!!   
390            allnames = [entry.Name for (entry, dummy) in allentries]
391            for name in names :   
392                if not self.matchString(name, allnames) :
393                    if options["groups"] :
394                        missinggroups[name] = 1
395                    else :   
396                        missingusers[name] = 1
397               
398            for (entry, entrypquota) in allentries :
399                if not changed.has_key(entry.Name) :
400                    changed[entry.Name] = {}
401                    if not options["groups"] :
402                        changed[entry.Name]["ingroups"] = []
403                       
404                if not entry.Exists :       
405                    # not found
406                    if options["add"] :
407                        # In case we want to add something, it is crucial
408                        # that we DON'T check with the system accounts files
409                        # like /etc/passwd because users may be defined
410                        # only remotely
411                        if self.isValidName(entry.Name) :
412                            reject = 0
413                            if rejectunknown :
414                                if options["groups"] :
415                                    try :
416                                        grp.getgrnam(entry.Name)
417                                    except KeyError :   
418                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
419                                        reject = 1
420                                else :   
421                                    try :
422                                        pwd.getpwnam(entry.Name)
423                                    except KeyError :   
424                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
425                                        reject = 1
426                            if not reject :       
427                                entry = getattr(self.storage, "add%s" % suffix)(entry)
428                        else :   
429                            if options["groups"] :
430                                self.printInfo(_("Invalid group name %s") % entry.Name)
431                            else :   
432                                self.printInfo(_("Invalid user name %s") % entry.Name)
433                    else :
434                        if options["groups"] :
435                            missinggroups[entry.Name] = 1
436                        else :   
437                            missingusers[entry.Name] = 1
438                elif options["delete"] :               
439                    todelete[entry.Name] = entry
440                               
441                if entry.Exists and (not entrypquota.Exists) :
442                    # not found
443                    if options["add"] :
444                        entrypquota = getattr(self.storage, "add%sPQuota" % suffix)(entry, printer)
445                       
446                if not entrypquota.Exists :     
447                    self.printInfo(_("Quota not found for object %s on printer %s.") % (entry.Name, printer.Name))
448                else :   
449                    if options["noquota"] or options["prototype"] or ((softlimit is not None) and (hardlimit is not None)) :
450                        entrypquota.setLimits(softlimit, hardlimit)
451                    if limitby :
452                        if changed[entry.Name].get("limitby") is None :
453                            entry.setLimitBy(limitby)
454                            changed[entry.Name]["limitby"] = limitby
455                   
456                    if options["reset"] :
457                        entrypquota.reset()
458                       
459                    if options["hardreset"] :   
460                        entrypquota.hardreset()
461                       
462                    if not options["groups"] :
463                        if used :
464                            entrypquota.setUsage(used)
465                           
466                        if overcharge is not None :   
467                            if changed[entry.Name].get("overcharge") is None :
468                                entry.setOverChargeFactor(overcharge)
469                                changed[entry.Name]["overcharge"] = overcharge
470                               
471                        if balance :
472                            if changed[entry.Name].get("balance") is None :
473                                if balance.startswith("+") or balance.startswith("-") :
474                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
475                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
476                                    entry.setAccountBalance(newbalance, newlifetimepaid)
477                                else :
478                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
479                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
480                                    entry.setAccountBalance(balancevalue, newlifetimepaid)
481                                changed[entry.Name]["balance"] = balance
482                               
483                        for groupname in groupnames :       
484                            # not executed if option --ingroups is not used
485                            if groupname not in changed[entry.Name]["ingroups"] :
486                                group = self.storage.getGroup(groupname)
487                                if group.Exists :
488                                    self.storage.addUserToGroup(entry, group)
489                                    changed[entry.Name]["ingroups"].append(groupname)
490                                else :
491                                    self.printInfo(_("Group %s not found in the PyKota Storage.") % groupname)
492                       
493        # Now outputs the list of nonexistent users and groups               
494        for name in missingusers.keys() :
495            self.printInfo(_("Nonexistent user %s") % name, level="warn")
496        for name in missinggroups.keys() :
497            self.printInfo(_("Nonexistent group %s") % name, level="warn")
498       
499        # Now delete what has to be deleted               
500        for (name, entry) in todelete.items() :               
501            entry.delete()
502                     
503if __name__ == "__main__" : 
504    retcode = 0
505    try :
506        defaults = { \
507                     "printer" : "*", \
508                   }
509        short_options = "vhdo:c:l:b:i:naugrp:P:S:H:G:RU:"
510        long_options = ["help", "version", "overcharge=", "charge=", "delete", "limitby=", "balance=", "ingroups=", "noquota", "add", "users", "groups", "reset", "hardreset", "prototype=", "printer=", "softlimit=", "hardlimit=", "pgroups=", "used="]
511       
512        # Initializes the command line tool
513        editor = EdPyKota(doc=__doc__)
514        editor.deferredInit()
515       
516        # parse and checks the command line
517        (options, args) = editor.parseCommandline(sys.argv[1:], short_options, long_options)
518       
519        # sets long options
520        options["help"] = options["h"] or options["help"]
521        options["version"] = options["v"] or options["version"]
522        options["add"] = options["a"] or options["add"]
523        options["users"] = options["u"] or options["users"]
524        options["groups"] = options["g"] or options["groups"]
525        options["prototype"] = options["p"] or options["prototype"]
526        options["printer"] = options["P"] or options["printer"] or defaults["printer"]
527        options["softlimit"] = options["S"] or options["softlimit"]
528        options["hardlimit"] = options["H"] or options["hardlimit"] 
529        options["reset"] = options["r"] or options["reset"] 
530        options["noquota"] = options["n"] or options["noquota"]
531        options["limitby"] = options["l"] or options["limitby"]
532        options["balance"] = options["b"] or options["balance"] 
533        options["delete"] = options["d"] or options["delete"] 
534        options["ingroups"] = options["i"] or options["ingroups"]
535        options["charge"] = options["c"] or options["charge"]
536        options["pgroups"] = options["G"] or options["pgroups"]
537        options["hardreset"] = options["R"] or options["hardreset"] 
538        options["used"] = options["U"] or options["used"]
539        options["overcharge"] = options["o"] or options["overcharge"]
540       
541        if options["help"] :
542            editor.display_usage_and_quit()
543        elif options["version"] :
544            editor.display_version_and_quit()
545        elif options["users"] and options["groups"] :   
546            raise PyKotaToolError, _("incompatible options, see help.")
547        elif (options["add"] or options["prototype"]) and options["delete"] :   
548            raise PyKotaToolError, _("incompatible options, see help.")
549        elif (options["reset"] or options["hardreset"] or options["limitby"] or options["used"] or options["balance"] or options["overcharge"] or options["softlimit"] or options["hardlimit"]) and options["prototype"] :
550            raise PyKotaToolError, _("incompatible options, see help.")
551        elif options["noquota"] and (options["prototype"] or options["hardlimit"] or options["softlimit"]) :
552            raise PyKotaToolError, _("incompatible options, see help.")
553        elif options["groups"] and (options["balance"] or options["ingroups"] or options["used"] or options["overcharge"]) :
554            raise PyKotaToolError, _("incompatible options, see help.")
555        else :
556            retcode = editor.main(args, options)
557    except KeyboardInterrupt :       
558        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
559    except SystemExit :       
560        pass
561    except :
562        try :
563            editor.crashed("edpykota failed")
564        except :   
565            crashed("edpykota failed")
566        retcode = -1
567
568    try :
569        editor.storage.close()
570    except (TypeError, NameError, AttributeError) :   
571        pass
572       
573    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.