root / pykota / trunk / bin / pkusers @ 2773

Revision 2773, 20.8 kB (checked in by jerome, 18 years ago)

pkusers is now optimized like pkprinters, pkbcodes and edpykota.

  • 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_
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            self.display("\n%s..." % _("Deletion"))
238            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
239            self.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               
266            if options["ingroups"] :
267                usersgroups = self.storage.getMatchingGroups(options["ingroups"])
268                if not usersgroups :
269                    raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
270            else :         
271                usersgroups = []
272                   
273            description = options["description"]
274            if description :
275                description = options["description"].strip()
276               
277            comment = options["comment"]
278            if comment :
279                comment = options["comment"].strip()
280            skipexisting = options["skipexisting"]   
281            groups = options["groups"]
282            remove = options["remove"]
283            self.storage.beginTransaction()
284            try :   
285                if options["add"] :   
286                    self.display("%s...\n" % _("Creation"))
287                    rejectunknown = self.config.getRejectUnknown()   
288                    nbtotal = len(names)
289                    for i in range(nbtotal) :
290                        ename = names[i]
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 = 100.0 * float(i) / float(nbtotal)
348                        self.display("\r%.02f%%" % percent)
349                else :
350                    self.display("\n%s...\n" % _("Modification"))
351                    nbtotal = len(entries)
352                    for i in range(nbtotal) :       
353                        entry = entries[i]
354                        if groups :
355                            self.modifyEntry(entry, groups, limitby, description)
356                        else :   
357                            self.modifyEntry(entry, groups, limitby, description, \
358                                             overcharge, balance, balancevalue, \
359                                             comment)
360                            self.manageUsersGroups(usersgroups, entry, remove)               
361                        entry.save()   
362                        percent = 100.0 * float(i) / float(nbtotal)
363                        self.display("\r%.02f%%" % percent)
364            except :                   
365                self.storage.rollbackTransaction()
366                raise
367            else :   
368                self.storage.commitTransaction()
369                               
370        if not options["list"] :               
371            self.done()
372                     
373if __name__ == "__main__" : 
374    retcode = 0
375    try :
376        defaults = { \
377                     "comment" : "", \
378                   }
379        short_options = "hvaD:dgl:rso:i:b:C:L"
380        long_options = ["help", "version", "add", "description=", \
381                        "delete", "groups", "list", "remove", \
382                        "skipexisting", "overcharge=", \
383                        "ingroups=", "limitby=", "balance=", "comment=", \
384                       ]
385                       
386       
387        # Initializes the command line tool
388        manager = PKUsers(doc=__doc__)
389        manager.deferredInit()
390       
391        # parse and checks the command line
392        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
393       
394        # sets long options
395        options["help"] = options["h"] or options["help"]
396        options["version"] = options["v"] or options["version"]
397        options["add"] = options["a"] or options["add"]
398        options["description"] = options["D"] or options["description"]
399        options["delete"] = options["d"] or options["delete"] 
400        options["groups"] = options["g"] or options["groups"]
401        options["list"] = options["L"] or options["list"]
402        options["remove"] = options["r"] or options["remove"]
403        options["skipexisting"] = options["s"] or options["skipexisting"]
404        options["limitby"] = options["l"] or options["limitby"]
405        options["balance"] = options["b"] or options["balance"] 
406        options["ingroups"] = options["i"] or options["ingroups"]
407        options["overcharge"] = options["o"] or options["overcharge"]
408        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
409       
410        if options["help"] :
411            manager.display_usage_and_quit()
412        elif options["version"] :
413            manager.display_version_and_quit()
414        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
415           or (options["skipexisting"] and not options["add"]) \
416           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
417           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
418            raise PyKotaCommandLineError, _("incompatible options, see help.")
419        elif options["remove"] and not options["ingroups"] :   
420            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
421        elif (not args) and (options["add"] or options["delete"]) :
422            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
423        else :
424            retcode = manager.main(args, options)
425    except KeyboardInterrupt :       
426        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
427        retcode = -3
428    except PyKotaCommandLineError, msg :   
429        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
430        retcode = -2
431    except SystemExit :       
432        pass
433    except :
434        try :
435            manager.crashed("pkusers failed")
436        except :   
437            crashed("pkusers failed")
438        retcode = -1
439
440    try :
441        manager.storage.close()
442    except (TypeError, NameError, AttributeError) :   
443        pass
444       
445    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.