root / pykota / trunk / bin / edpykota @ 2147

Revision 2147, 26.6 kB (checked in by jerome, 19 years ago)

Removed all references to $Log$

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