root / pykota / trunk / bin / pkusers @ 2782

Revision 2782, 20.6 kB (checked in by jerome, 18 years ago)

Code cleaning.
Topped to 10000 the number of times the percent will be displayed by not displaying it if there's no change.
This is very useful when adding 25000 users on 300 printers through an ssh connection...

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