root / pykota / trunk / bin / pkusers @ 2723

Revision 2723, 19.9 kB (checked in by jerome, 18 years ago)

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