root / pykota / trunk / bin / pkusers @ 3288

Revision 3288, 21.4 kB (checked in by jerome, 16 years ago)

Moved all exceptions definitions to a dedicated module.

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