root / pykota / trunk / bin / pkusers @ 3413

Revision 3413, 20.5 kB (checked in by jerome, 16 years ago)

Removed unnecessary spaces at EOL.

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