root / pykota / trunk / bin / pkusers @ 2739

Revision 2725, 20.2 kB (checked in by jerome, 19 years ago)

Doesn't list anymore entries which don't exist.

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