root / pykota / trunk / bin / pkusers @ 2707

Revision 2707, 19.8 kB (checked in by jerome, 18 years ago)

Improved user modification speed by around 15%.

  • 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 Users Manager
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 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 os
28import sys
29import pwd
30import grp
31
32from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
33
34__doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s
35
36An Users and Groups Manager for PyKota.
37
38command line usage :
39
40  pkusers [options] user1 user2 user3 ... userN
41 
42or : 
43
44  pkusers --groups [options] group1 group2 group3 ... groupN
45
46options :
47
48  -v | --version       Prints pkusers's version number then exits.
49  -h | --help          Prints this message then exits.
50 
51  -a | --add           Adds users if they don't exist on the database.
52                       If they exist, they are modified unless
53                       -s|--skipexisting is also used.
54                       
55  -d | --delete        Deletes users from the quota storage.
56 
57  -D | --description d Adds a textual description to users or groups.
58                       
59  -g | --groups        Edit users groups instead of users.
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 such 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  -l | --limitby l     Choose if the user/group is limited in printing                     
76                       by its account balance or by its page quota.
77                       The default value is 'quota'. Allowed values
78                       are 'quota' 'balance' 'noquota' 'noprint' 
79                       and 'nochange' :
80                       
81                         - quota : limit by number of pages per printer.
82                         - balance : limit by number of credits in account.
83                         - noquota : no limit, accounting still done.
84                         - nochange : no limit, accounting not done.
85                         - noprint : printing is denied.
86                       NB : nochange and noprint are not supported for groups.
87                       
88  -b | --balance b     Sets the user's account balance to b.                     
89                       Account balance may be increase or decreased
90                       if b is prefixed with + or -.
91                       WARNING : when decreasing account balance,
92                       the total paid so far by the user is decreased
93                       too.
94                       Groups don't have a real balance, but the
95                       sum of their users' account balance.
96                       
97  -C | --comment txt   Defines some informational text to be associated
98                       with a change to an user's account balance.
99                       Only meaningful if -b | --balance is also used.
100                       
101                       
102  -r | --remove        In combination with the --groups option above,                       
103                       remove users from the specified users groups.
104                       
105  -s | --skipexisting  In combination with the --add option above, tells
106                       pkusers to not modify existing users.
107                       
108  user1 through userN and group1 through groupN can use wildcards
109  if the --add option is not set.
110 
111examples :                             
112
113  $ pkusers --add john paul george ringo/ringo@example.com
114 
115  This will add users john, paul, george and ringo to the quota
116  database. User ringo's email address will also be set to
117  'ringo@example.com'
118 
119  $ pkusers --ingroups coders,it jerome
120 
121  User jerome is put into the groups "coders" and "it" which must
122  already exist in the quota database.
123           
124  $ pkusers --limitby balance jerome
125 
126  This will tell PyKota to limit jerome by his account's balance
127  when printing.
128 
129  $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome
130 
131  This will increase jerome's account balance by 10.0 (in your
132  own currency). You can decrease the account balance with a
133  dash prefix, and set it to a fixed amount with no prefix.
134  A comment will be stored for this balance change.
135 
136  $ pkusers --delete jerome rachel
137 
138  This will completely delete jerome and rachel from the quota
139  database. All their quotas and jobs will be deleted too.
140 
141  $ pkusers --overcharge 2.5 poorstudent
142 
143  This will overcharge the poorstudent user by a factor of 2.5.
144 
145  $ pkusers --overcharge -1 jerome
146 
147  User jerome will actually earn money whenever he prints.
148 
149  $ pkusers --overcharge 0 boss
150 
151  User boss can print at will, it won't cost him anything because the
152  cost of each print job will be multiplied by zero before charging
153  his account.
154""")
155       
156class PKUsers(PyKotaTool) :       
157    """A class for a users and users groups manager."""
158    def main(self, names, options) :
159        """Manage users or groups."""
160        if not self.config.isAdmin :
161            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
162           
163        suffix = (options["groups"] and "Group") or "User"       
164       
165        if options["delete"] :   
166            self.display("%s...\n" % _("Deletion"))
167            todelete = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
168            nbtotal = len(todelete)
169            for i in range(nbtotal) :
170                todelete[i].delete()
171                percent = 100.0 * float(i) / float(nbtotal)
172                self.display("\r%.02f%%" % percent)
173        else :
174            if options["add"] :   
175                self.display("%s...\n" % _("Creation"))
176                rejectunknown = self.config.getRejectUnknown()   
177                entries = []
178                nbtotal = len(names)
179                for i in range(nbtotal) :
180                    ename = names[i]
181                    email = ""
182                    if not options["groups"] :
183                        splitname = ename.split('/', 1)     # username/email
184                        if len(splitname) == 1 :
185                            splitname.append("")
186                        (ename, email) = splitname
187                        if email and (email.count('@') != 1) :
188                            self.printInfo(_("Invalid email address %s") % email)
189                            email = ""
190                    entry = getattr(self.storage, "get%s" % suffix)(ename)
191                    if entry.Exists :
192                        if options["skipexisting"] :
193                            self.printInfo(_("%s %s already exists, skipping.") % (suffix, entry.Name))
194                        else :   
195                            self.printInfo(_("%s %s already exists, will be modified.") % (suffix, entry.Name))
196                            entries.append(entry)
197                    else :
198                        if self.isValidName(entry.Name) :
199                            reject = 0
200                            if rejectunknown :
201                                if options["groups"] :
202                                    try :
203                                        grp.getgrnam(entry.Name)
204                                    except KeyError :   
205                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
206                                        reject = 1
207                                else :   
208                                    try :
209                                        pwd.getpwnam(entry.Name)
210                                    except KeyError :   
211                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
212                                        reject = 1
213                            if not reject :       
214                                if email :
215                                    entry.Email = email
216                                entry = getattr(self.storage, "add%s" % suffix)(entry)
217                                entries.append(entry)
218                        else :   
219                            if options["groups"] :
220                                self.printInfo(_("Invalid group name %s") % entry.Name)
221                            else :   
222                                self.printInfo(_("Invalid user name %s") % entry.Name)
223                               
224                    percent = 100.0 * float(i) / float(nbtotal)
225                    self.display("\r%.02f%%" % percent)
226                self.display("\r100.00%%\r        \r%s\n" % _("Done."))
227            else :       
228                if not names :
229                    names = ["*"]
230                entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
231                if not entries :
232                    raise PyKotaCommandLineError, _("There's no %s matching %s") % (suffix.lower(), " ".join(names))
233                       
234            if options["list"] :
235                if suffix == "User" :
236                    maildomain = self.config.getMailDomain()
237                    smtpserver = self.config.getSMTPServer()
238                    for entry in entries :
239                        email = entry.Email
240                        if not email :
241                            if maildomain :     
242                                email = "%s@%s" % (entry.Name, maildomain)
243                            elif smtpserver :   
244                                email = "%s@%s" % (entry.Name, smtpserver)
245                            else :   
246                                email = "%s@%s" % (entry.Name, "localhost")
247                        msg = "%s - <%s>" % (entry.Name, email)
248                        if entry.Description :
249                            msg += " - %s" % entry.Description
250                        print msg   
251                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
252                        print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
253                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
254                        print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
255                else :   
256                    for entry in entries :
257                        msg = "%s" % entry.Name
258                        if entry.Description :
259                            msg += " - %s" % entry.Description
260                        print msg   
261                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
262                        print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
263                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
264            else :
265                self.display("%s...\n" % _("Modification"))
266               
267                limitby = options["limitby"]
268                if limitby :
269                    limitby = limitby.strip().lower()
270                if limitby :
271                    if limitby not in ('quota', 'balance', 'noquota', \
272                                                'noprint', 'nochange') :
273                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
274                    if (limitby in ('nochange', 'noprint')) and options["groups"] :   
275                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
276                   
277                overcharge = options["overcharge"]
278                if overcharge :
279                    try :
280                        overcharge = float(overcharge.strip())
281                    except (ValueError, AttributeError) :   
282                        raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
283                       
284                balance = options["balance"]
285                if balance :
286                    balance = balance.strip()
287                    try :
288                        balancevalue = float(balance)
289                    except ValueError :   
290                        raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
291                   
292                if options["ingroups"] :
293                    usersgroups = self.storage.getMatchingGroups(options["ingroups"])
294                    if not usersgroups :
295                        raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
296                else :         
297                    usersgroups = []
298                       
299                description = options["description"]
300                if description :
301                    description = options["description"].strip()
302                   
303                comment = options["comment"]
304                if comment :
305                    comment = options["comment"].strip()
306                   
307                nbtotal = len(entries)
308                for i in range(nbtotal) :       
309                    entry = entries[i]
310                   
311                    # We need a transaction because we may have to create a record
312                    # in the payments' table at the same time we modify the user's balance.
313                    self.storage.beginTransaction()
314                    try :
315                        if description is not None : # NB : "" is allowed !
316                            entry.setDescription(description)
317                        if limitby :   
318                            entry.setLimitBy(limitby)
319                           
320                        if suffix == "User" :   
321                            if overcharge is not None : # NB : 0 is allowed !     
322                                entry.setOverChargeFactor(overcharge)
323                       
324                        if suffix == "User" :
325                            if balance :
326                                if balance.startswith("+") or balance.startswith("-") :
327                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
328                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
329                                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
330                                else :
331                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
332                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
333                                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
334                                           
335                            for ugroup in usersgroups :
336                                if options["remove"] :
337                                    ugroup.delUserFromGroup(entry)
338                                else :
339                                    ugroup.addUserToGroup(entry)
340                        entry.save()   
341                    except :
342                        self.storage.rollbackTransaction()
343                        raise
344                    else :   
345                        self.storage.commitTransaction()
346
347                    percent = 100.0 * float(i) / float(nbtotal)
348                    self.display("\r%.02f%%" % percent)
349                               
350        if not options["list"] :               
351            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
352                     
353if __name__ == "__main__" : 
354    retcode = 0
355    try :
356        defaults = { \
357                     "comment" : "", \
358                   }
359        short_options = "hvaD:dgl:rso:i:b:C:L"
360        long_options = ["help", "version", "add", "description=", \
361                        "delete", "groups", "list", "remove", \
362                        "skipexisting", "overcharge=", \
363                        "ingroups=", "limitby=", "balance=", "comment=", \
364                       ]
365                       
366       
367        # Initializes the command line tool
368        manager = PKUsers(doc=__doc__)
369        manager.deferredInit()
370       
371        # parse and checks the command line
372        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
373       
374        # sets long options
375        options["help"] = options["h"] or options["help"]
376        options["version"] = options["v"] or options["version"]
377        options["add"] = options["a"] or options["add"]
378        options["description"] = options["D"] or options["description"]
379        options["delete"] = options["d"] or options["delete"] 
380        options["groups"] = options["g"] or options["groups"]
381        options["list"] = options["L"] or options["list"]
382        options["remove"] = options["r"] or options["remove"]
383        options["skipexisting"] = options["s"] or options["skipexisting"]
384        options["limitby"] = options["l"] or options["limitby"]
385        options["balance"] = options["b"] or options["balance"] 
386        options["ingroups"] = options["i"] or options["ingroups"]
387        options["overcharge"] = options["o"] or options["overcharge"]
388        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
389       
390        if options["help"] :
391            manager.display_usage_and_quit()
392        elif options["version"] :
393            manager.display_version_and_quit()
394        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
395           or (options["skipexisting"] and not options["add"]) \
396           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
397           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
398            raise PyKotaCommandLineError, _("incompatible options, see help.")
399        elif options["remove"] and not options["ingroups"] :   
400            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
401        elif (not args) and options["add"] :
402            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
403        else :
404            retcode = manager.main(args, options)
405    except KeyboardInterrupt :       
406        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
407        retcode = -3
408    except PyKotaCommandLineError, msg :   
409        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
410        retcode = -2
411    except SystemExit :       
412        pass
413    except :
414        try :
415            manager.crashed("pkusers failed")
416        except :   
417            crashed("pkusers failed")
418        retcode = -1
419
420    try :
421        manager.storage.close()
422    except (TypeError, NameError, AttributeError) :   
423        pass
424       
425    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.