root / pykota / trunk / bin / pkusers @ 2724

Revision 2724, 20.0 kB (checked in by jerome, 18 years ago)

Skip lines between entries while listing them.

  • 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                        email = entry.Email
244                        if not email :
245                            if maildomain :     
246                                email = "%s@%s" % (entry.Name, maildomain)
247                            elif smtpserver :   
248                                email = "%s@%s" % (entry.Name, smtpserver)
249                            else :   
250                                email = "%s@%s" % (entry.Name, "localhost")
251                        msg = "%s - <%s>" % (entry.Name, email)
252                        if entry.Description :
253                            msg += " - %s" % entry.Description
254                        print msg   
255                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
256                        print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
257                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
258                        print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
259                        print
260                else :   
261                    for entry in entries :
262                        msg = "%s" % entry.Name
263                        if entry.Description :
264                            msg += " - %s" % entry.Description
265                        print msg   
266                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
267                        print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
268                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
269                        print
270            else :
271                self.display("%s...\n" % _("Modification"))
272               
273                limitby = options["limitby"]
274                if limitby :
275                    limitby = limitby.strip().lower()
276                if limitby :
277                    if limitby not in ('quota', 'balance', 'noquota', \
278                                                'noprint', 'nochange') :
279                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
280                    if (limitby in ('nochange', 'noprint')) and options["groups"] :   
281                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
282                   
283                overcharge = options["overcharge"]
284                if overcharge :
285                    try :
286                        overcharge = float(overcharge.strip())
287                    except (ValueError, AttributeError) :   
288                        raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
289                       
290                balance = options["balance"]
291                if balance :
292                    balance = balance.strip()
293                    try :
294                        balancevalue = float(balance)
295                    except ValueError :   
296                        raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
297                   
298                if options["ingroups"] :
299                    usersgroups = self.storage.getMatchingGroups(options["ingroups"])
300                    if not usersgroups :
301                        raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
302                else :         
303                    usersgroups = []
304                       
305                description = options["description"]
306                if description :
307                    description = options["description"].strip()
308                   
309                comment = options["comment"]
310                if comment :
311                    comment = options["comment"].strip()
312                   
313                nbtotal = len(entries)
314                for i in range(nbtotal) :       
315                    entry = entries[i]
316                   
317                    # We need a transaction because we may have to create a record
318                    # in the payments' table at the same time we modify the user's balance.
319                    self.storage.beginTransaction()
320                    try :
321                        if description is not None : # NB : "" is allowed !
322                            entry.setDescription(description)
323                        if limitby :   
324                            entry.setLimitBy(limitby)
325                           
326                        if suffix == "User" :   
327                            if overcharge is not None : # NB : 0 is allowed !     
328                                entry.setOverChargeFactor(overcharge)
329                       
330                        if suffix == "User" :
331                            if balance :
332                                if balance.startswith("+") or balance.startswith("-") :
333                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
334                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
335                                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
336                                else :
337                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
338                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
339                                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
340                                           
341                            for ugroup in usersgroups :
342                                if options["remove"] :
343                                    ugroup.delUserFromGroup(entry)
344                                else :
345                                    ugroup.addUserToGroup(entry)
346                        entry.save()   
347                    except :
348                        self.storage.rollbackTransaction()
349                        raise
350                    else :   
351                        self.storage.commitTransaction()
352
353                    percent = 100.0 * float(i) / float(nbtotal)
354                    self.display("\r%.02f%%" % percent)
355                               
356        if not options["list"] :               
357            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
358                     
359if __name__ == "__main__" : 
360    retcode = 0
361    try :
362        defaults = { \
363                     "comment" : "", \
364                   }
365        short_options = "hvaD:dgl:rso:i:b:C:L"
366        long_options = ["help", "version", "add", "description=", \
367                        "delete", "groups", "list", "remove", \
368                        "skipexisting", "overcharge=", \
369                        "ingroups=", "limitby=", "balance=", "comment=", \
370                       ]
371                       
372       
373        # Initializes the command line tool
374        manager = PKUsers(doc=__doc__)
375        manager.deferredInit()
376       
377        # parse and checks the command line
378        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
379       
380        # sets long options
381        options["help"] = options["h"] or options["help"]
382        options["version"] = options["v"] or options["version"]
383        options["add"] = options["a"] or options["add"]
384        options["description"] = options["D"] or options["description"]
385        options["delete"] = options["d"] or options["delete"] 
386        options["groups"] = options["g"] or options["groups"]
387        options["list"] = options["L"] or options["list"]
388        options["remove"] = options["r"] or options["remove"]
389        options["skipexisting"] = options["s"] or options["skipexisting"]
390        options["limitby"] = options["l"] or options["limitby"]
391        options["balance"] = options["b"] or options["balance"] 
392        options["ingroups"] = options["i"] or options["ingroups"]
393        options["overcharge"] = options["o"] or options["overcharge"]
394        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
395       
396        if options["help"] :
397            manager.display_usage_and_quit()
398        elif options["version"] :
399            manager.display_version_and_quit()
400        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
401           or (options["skipexisting"] and not options["add"]) \
402           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
403           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
404            raise PyKotaCommandLineError, _("incompatible options, see help.")
405        elif options["remove"] and not options["ingroups"] :   
406            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
407        elif (not args) and options["add"] :
408            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
409        else :
410            retcode = manager.main(args, options)
411    except KeyboardInterrupt :       
412        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
413        retcode = -3
414    except PyKotaCommandLineError, msg :   
415        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
416        retcode = -2
417    except SystemExit :       
418        pass
419    except :
420        try :
421            manager.crashed("pkusers failed")
422        except :   
423            crashed("pkusers failed")
424        retcode = -1
425
426    try :
427        manager.storage.close()
428    except (TypeError, NameError, AttributeError) :   
429        pass
430       
431    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.