root / pykota / trunk / bin / pkusers @ 3260

Revision 3260, 21.3 kB (checked in by jerome, 16 years ago)

Changed license to GNU GPL v3 or later.
Changed Python source encoding from ISO-8859-15 to UTF-8 (only ASCII
was used anyway).

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