#!/usr/bin/python
# -*- coding: ISO-8859-15 -*-

# PyKota Print Quota Editor 
#
# PyKota - Print Quotas for CUPS and LPRng
#
# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: edpykota,v 1.74 2004/06/07 18:43:40 jalet Exp $
#
# $Log: edpykota,v $
# Revision 1.74  2004/06/07 18:43:40  jalet
# Fixed over-verbose exits when displaying help or version number
#
# Revision 1.73  2004/06/03 21:50:34  jalet
# Improved error logging.
# crashrecipient directive added.
# Now exports the job's size in bytes too.
#
# Revision 1.72  2004/04/16 16:20:19  jalet
# Note about not implemented limitby values
#
# Revision 1.71  2004/03/24 15:15:24  jalet
# Began integration of Henrik Janhagen's work on quota-then-balance
# and balance-then-quota
#
# Revision 1.70  2004/02/27 09:23:21  jalet
# Minor code reorganisation
#
# Revision 1.69  2004/02/19 15:05:56  jalet
# domain names changed to example.com in the doc, according to RFC2606
#
# Revision 1.68  2004/01/28 10:05:22  jalet
# New user/group deletion code
#
# Revision 1.67  2004/01/15 11:19:07  jalet
# Typos in messages wrt gettext.
#
# Revision 1.66  2004/01/12 21:54:36  jalet
# User's email address can now be set at user's creation time.
#
# Revision 1.65  2004/01/08 16:24:49  jalet
# edpykota now supports adding printers to printer groups.
#
# Revision 1.64  2004/01/08 14:10:32  jalet
# Copyright year changed.
#
# Revision 1.63  2003/11/24 16:50:58  jalet
# Old help message deletedd
#
# Revision 1.62  2003/11/12 23:28:38  jalet
# More work on new backend. This commit may be unstable.
#
# Revision 1.61  2003/11/12 13:06:35  jalet
# Bug fix wrt no user/group name command line argument to edpykota
#
# Revision 1.60  2003/10/09 21:25:24  jalet
# Multiple printer names or wildcards can be passed on the command line
# separated with commas.
# Beta phase.
#
# Revision 1.59  2003/10/07 09:07:27  jalet
# Character encoding added to please latest version of Python
#
# Revision 1.58  2003/10/03 12:27:01  jalet
# Several optimizations, especially with LDAP backend
#
# Revision 1.57  2003/08/20 16:01:19  jalet
# Comment added.
#
# Revision 1.56  2003/07/29 20:55:17  jalet
# 1.14 is out !
#
# Revision 1.55  2003/07/28 09:11:12  jalet
# PyKota now tries to add its attributes intelligently in existing LDAP
# directories.
#
# Revision 1.54  2003/07/21 06:32:42  jalet
# Prevents email messages to be sent at modification/creation time for
# a user/group quota
#
# Revision 1.53  2003/07/09 06:03:41  jalet
# Fixed typo when using edpykota --prototype
#
# Revision 1.52  2003/07/07 12:11:13  jalet
# Small fix
#
# Revision 1.51  2003/07/07 11:55:50  jalet
# Small fix
#
# Revision 1.50  2003/07/05 12:33:53  jalet
# More on previous fix.
#
# Revision 1.49  2003/07/05 12:32:07  jalet
# Ensure that the user don't pass more than two prices for a printer.
#
# Revision 1.48  2003/06/25 19:52:30  jalet
# Should be ready for testing :-)
#
# Revision 1.47  2003/06/25 14:10:01  jalet
# Hey, it may work (edpykota --reset excepted) !
#
# Revision 1.46  2003/06/16 11:59:09  jalet
# More work on LDAP
#
# Revision 1.45  2003/06/11 19:32:00  jalet
# Severe bug wrt account balance setting should be corrected.
#
# Revision 1.44  2003/04/29 22:03:38  jalet
# Better error handling.
#
# Revision 1.43  2003/04/23 22:13:56  jalet
# Preliminary support for LPRng added BUT STILL UNTESTED.
#
# Revision 1.42  2003/04/17 13:38:47  jalet
# Docstring corrected for better manual page
#
# Revision 1.41  2003/04/16 12:35:49  jalet
# Groups quota work now !
#
# Revision 1.40  2003/04/16 08:22:09  jalet
# More strict error detection.
# Minor code rewrite to avoid some repetitive tests.
#
# Revision 1.39  2003/04/16 08:01:53  jalet
# edpykota --charge command line option works now.
#
# Revision 1.38  2003/04/15 22:02:43  jalet
# More complete docstring
#
# Revision 1.37  2003/04/15 21:58:33  jalet
# edpykota now accepts a --delete option.
# Preparation to allow edpykota to accept much more command line options
# (WARNING : docstring is OK, but code isn't !)
#
# Revision 1.36  2003/04/15 13:55:28  jalet
# Options --limitby and --balance added to edpykota
#
# Revision 1.35  2003/04/15 13:06:39  jalet
# Allow to add a printer without any user
#
# Revision 1.34  2003/04/11 16:51:11  jalet
# Bug fix for edpykota --add with users who already had a quota on the printer.
#
# Revision 1.33  2003/04/10 21:47:20  jalet
# Job history added. Upgrade script neutralized for now !
#
# Revision 1.32  2003/04/08 21:31:39  jalet
# (anything or 0) = anything !!! Go back to school Jerome !
#
# Revision 1.31  2003/04/08 21:13:44  jalet
# Prepare --groups option to work.
#
# Revision 1.30  2003/04/08 21:10:18  jalet
# Checks --groups option presence instead of --users because --users is the default.
#
# Revision 1.29  2003/04/05 09:28:56  jalet
# Unnecessary message was logged
#
# Revision 1.28  2003/03/29 13:45:26  jalet
# GPL paragraphs were incorrectly (from memory) copied into the sources.
# Two README files were added.
# Upgrade script for PostgreSQL pre 1.01 schema was added.
#
# Revision 1.27  2003/03/10 00:23:04  jalet
# Bad english
#
# Revision 1.26  2003/03/10 00:11:27  jalet
# Cleaner example.
#
# Revision 1.25  2003/03/09 23:56:21  jalet
# Option noquota added to do accounting only.
#
# Revision 1.24  2003/02/27 23:48:41  jalet
# Correctly maps PyKota's log levels to syslog log levels
#
# Revision 1.23  2003/02/27 22:55:20  jalet
# WARN log priority doesn't exist.
#
# Revision 1.22  2003/02/27 09:37:02  jalet
# Wildcards seem to work now
#
# Revision 1.21  2003/02/27 09:04:46  jalet
# user and group names can be passed as wildcards if the --add option
# is not set. The default is to act on all users or groups.
#
# Revision 1.20  2003/02/10 12:07:30  jalet
# Now repykota should output the recorded total page number for each printer too.
#
# Revision 1.19  2003/02/09 13:40:29  jalet
# typo
#
# Revision 1.18  2003/02/09 12:56:53  jalet
# Internationalization begins...
#
# Revision 1.17  2003/02/08 22:47:23  jalet
# Option --reset can now be used without having to use soft and hard limits
# on the command line.
#
# Revision 1.16  2003/02/08 22:39:46  jalet
# --reset command line option added
#
# Revision 1.15  2003/02/08 22:20:01  jalet
# Clarification on why we don't check with /etc/passwd to see if the user
# name is valid or not.
#
# Revision 1.14  2003/02/08 22:18:15  jalet
# Now checks user and group names for validity before adding them
#
# Revision 1.13  2003/02/08 22:09:02  jalet
# Only printer was added the first time.
#
# Revision 1.12  2003/02/08 21:44:49  jalet
# Python 2.1 string module doesn't define ascii_letters
#
# Revision 1.11  2003/02/08 09:42:44  jalet
# Better handle wrong or bad command line arguments
#
# Revision 1.10  2003/02/08 09:39:20  jalet
# typos
#
# Revision 1.9  2003/02/08 09:38:06  jalet
# Badly placed test
#
# Revision 1.8  2003/02/07 22:53:57  jalet
# Checks if printer name is valid before adding it
#
# Revision 1.7  2003/02/07 22:17:58  jalet
# Incomplete test
#
# Revision 1.6  2003/02/07 22:13:13  jalet
# Perhaps edpykota is now able to add printers !!! Oh, stupid me !
#
# Revision 1.5  2003/02/06 14:49:04  jalet
# edpykota should be ok now
#
# Revision 1.4  2003/02/06 14:28:59  jalet
# edpykota should be ok, minus some typos
#
# Revision 1.3  2003/02/06 10:47:21  jalet
# Documentation string and command line options didn't match.
#
# Revision 1.2  2003/02/06 10:39:23  jalet
# Preliminary edpykota work.
#
# Revision 1.1  2003/02/05 21:41:09  jalet
# Skeletons added for all command line tools
#
#
#

import sys

from pykota import version
from pykota.tool import PyKotaTool, PyKotaToolError
from pykota.config import PyKotaConfigError
from pykota.storage import PyKotaStorageError

__doc__ = """edpykota v%s (c) 2003-2004 C@LL - Conseil Internet & Logiciels Libres
A Print Quota editor for PyKota.

command line usage :

  edpykota [options] user1 user2 ... userN
  
  edpykota [options] group1 group2 ... groupN

options :

  -v | --version       Prints edpykota's version number then exits.
  -h | --help          Prints this message then exits.
  
  -a | --add           Adds users and/or printers if they don't 
                       exist on the Quota Storage Server.
                       
  -d | --delete        Deletes users/groups from the quota storage.
                       Printers are never deleted.
                       
  -c | --charge p[,j]  Sets the price per page and per job to charge
                       for a particular printer. Job price is optional.
                       If both are to be set, separate them with a comma.
                       Floating point values are allowed.
  
  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
                              listed, separated by commas. The groups
                              must already exist in the Quota Storage.
  
  -u | --users         Edit users print quotas, this is the default.
  
  -P | --printer p     Edit quotas on printer p only. Actually p can
                       use wildcards characters to select only
                       some printers. The default value is *, meaning
                       all printers. 
                       You can specify several names or wildcards, 
                       by separating them with commas.
  
  -G | --pgroups pg1[,pg2...] Adds the printer(s) to the printer groups
                       pg1, pg2, etc... which must already exist.
                       A printer group is just like a normal printer,
                       only that it is usually unknown from the printing
                       system. Create printer groups exactly the same
                       way that you create printers, then add other 
                       printers to them with this option.
                       Accounting is done on a printer and on all
                       the printer groups it belongs to, quota checking
                       is done on a printer and on all the printer groups
                       it belongs to.
  
  -g | --groups        Edit users groups print quotas instead of users.
                          
  -p | --prototype u|g Uses user u or group g as a prototype to set
                       print quotas
                       
  -n | --noquota       Doesn't set a quota but only does accounting.
  
  -r | --reset         Resets the printed page counter for the user
                       or group to zero. The life time page counter 
                       is kept unchanged.
                       
  -l | --limitby l     Choose if the user/group is limited in printing                     
                       by its account balance or by its page quota.
                       The default value is 'quota'. Allowed values
                       are 'quota' 'balance' 'quota-then-balance' and
                       'balance-then-quota'.
                       WARNING : quota-then-balance and balance-then-quota
                       are not yet implemented.
                       
  -b | --balance b     Sets the user's account balance to b.                     
                       Account balance may be increase or decreased
                       if b is prefixed with + or -.
                       WARNING : when decreasing account balance,
                       the total paid so far by the user is decreased
                       too.
                       Groups don't have a real balance, but the
                       sum of their users' account balance.
                       
  -S | --softlimit sl  Sets the quota soft limit to sl pages.                       
  
  -H | --hardlimit hl  Sets the quota hard limit to hl pages.
  
  user1 through userN and group1 through groupN can use wildcards
  if the --add option is not set.
  
examples :                              

  $ edpykota --add -p jerome john paul george ringo/ringo@example.com
  
  This will add users john, paul, george and ringo to the quota
  database, and set their print quotas to the same values than user 
  jerome. User jerome must already exist.
  User ringo's email address will also be set to 'ringo@example.com'
  
  $ edpykota --printer lp -S 50 -H 60 jerome
  
  This will set jerome's print quota on the lp printer to a soft limit
  of 50 pages, and a hard limit of 60 pages. If either user jerome or
  printer lp doesn't exist on the Quota Storage Server then nothing is done.

  $ edpykota --add --printer lp --ingroups coders,it -S 50 -H 60 jerome
  
  Same as above, but if either user jerome or printer lp doesn't exist 
  on the Quota Storage Server they are automatically added. Also
  user jerome is put into the groups "coders" and "it" which must
  already exist in the Quota Storage.
            
  $ edpykota -g -S 500 -H 550 financial support            
  
  This will set print quota soft limit to 500 pages and hard limit
  to 550 pages for groups financial and support on all printers.
  
  $ edpykota --reset jerome "jo*"
  
  This will reset jerome's page counter to zero on all printers, as
  well as every user whose name begins with 'jo'.
  Their life time page counter on each printer will be kept unchanged.
  
  $ edpykota --printer hpcolor --noquota jerome
  
  This will tell PyKota to not limit jerome when printing on the 
  hpcolor printer. All his jobs will be allowed on this printer, but 
  accounting of the pages he prints will still be kept.
  Print Quotas for jerome on other printers are unchanged.
  
  $ edpykota --limitby balance jerome
  
  This will tell PyKota to limit jerome by his account's balance
  when printing.
  
  $ edpykota --balance +10.0 jerome
  
  This will increase jerome's account balance by 10.0 (in your
  own currency). You can decrease the account balance with a
  dash prefix, and set it to a fixed amount with no prefix.
  
  $ edpykota --delete jerome rachel
  
  This will completely delete jerome and rachel from the Quota Storage
  database. All their quotas and jobs will be deleted too.
  
  $ edpykota --printer lp --charge 0.1
  
  This will set the page price for printer lp to 0.1. Job price
  will not be changed.
  
  $ edpykota --printer hplj1,hplj2 --pgroups Laser,HP
  
  This will put printers hplj1 and hplj2 in printers groups Laser and HP.
  When printing either on hplj1 or hplj2, print quota will also be 
  checked and accounted for on virtual printers Laser and HP.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

Please e-mail bugs to: %s""" % (version.__version__, version.__author__)
        
class EdPyKota(PyKotaTool) :        
    """A class for edpykota."""
    def main(self, names, options) :
        """Edit user or group quotas."""
        
        suffix = (options["groups"] and "Group") or "User"        
        
        softlimit = hardlimit = None    
        if not options["noquota"] :
            if options["softlimit"] :
                try :
                    softlimit = int(options["softlimit"].strip())
                except ValueError :    
                    raise PyKotaToolError, _("Invalid softlimit value %s.") % options["softlimit"]
            if options["hardlimit"] :
                try :
                    hardlimit = int(options["hardlimit"].strip())
                except ValueError :    
                    raise PyKotaToolError, _("Invalid hardlimit value %s.") % options["hardlimit"]
            if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :        
                # error, exchange them
                self.logger.log_message(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
                (softlimit, hardlimit) = (hardlimit, softlimit)
            
        balance = options["balance"]
        if balance :
            balance = balance.strip()
            try :
                balancevalue = float(balance)
            except ValueError :    
                raise PyKotaToolError, _("Invalid balance value %s") % options["balance"]
            
        if options["charge"] :
            try :
                charges = [float(part) for part in options["charge"].split(',', 1)]
            except ValueError :    
                raise PyKotaToolError, _("Invalid charge amount value %s") % options["charge"]
            else :    
                if len(charges) > 2 :
                    charges = charges[:2]
                if len(charges) != 2 :
                    charges = [charges[0], None]
                    
        limitby = options["limitby"]
        if limitby :
            limitby = limitby.strip().lower()
        if limitby and (limitby not in ('quota', 'balance', 'quota-then-balance', 'balance-then-quota')) :    
            raise PyKotaToolError, _("Invalid limitby value %s") % options["limitby"]
            
        if options["ingroups"] :    
            groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
        else :    
            groupnames = []
            
        if options["prototype"] :    
            protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
            
        printeradded = 0
        printers = self.storage.getMatchingPrinters(options["printer"])
        if not printers :
            pname = options["printer"]
            if options["add"] and pname :
                if self.isValidName(pname) :
                    printers = [ self.storage.addPrinter(pname) ]
                    if printers[0].Exists :
                        printeradded = 1
                    else :    
                        raise PyKotaToolError, _("Impossible to add printer %s") % pname
                else :    
                    raise PyKotaToolError, _("Invalid printer name %s") % pname
            else :
                raise PyKotaToolError, _("There's no printer matching %s") % pname
        if not names :    
            if options["add"] :
                if not printeradded :
                    raise PyKotaToolError, _("You have to pass user or group names on the command line")
                else :    
                    names = getattr(self.storage, "getAll%ssNames" % suffix)()
            else :    
                names = [ "*" ] # all users
                
        printersgroups = []        
        if options["pgroups"] :        
            printersgroups = self.storage.getMatchingPrinters(options["pgroups"])
            
        todelete = {}    
        changed = {} # tracks changes made at the user/group level
        for printer in printers :
            for pgroup in printersgroups :
                pgroup.addPrinterToGroup(printer)    
                
            if options["charge"] :
                (perpage, perjob) = charges
                printer.setPrices(perpage, perjob)    
                
            if options["prototype"] :
                if protoentry.Exists :
                    protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
                    if not protoquota.Exists :
                        self.logger.log_message(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
                        continue    # skip this printer
                    else :    
                        (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
                else :        
                    self.logger.log_message(_("Prototype object %s not found in Quota Storage.") % protoentry.Name)
                    
            if not options["noquota"] :    
                if hardlimit is None :    
                    hardlimit = softlimit
                    if hardlimit is not None :
                        self.logger.log_message(_("Undefined hard limit set to soft limit (%s) on printer %s.") % (str(hardlimit), printer.Name))
                if softlimit is None :    
                    softlimit = hardlimit
                    if softlimit is not None :
                        self.logger.log_message(_("Undefined soft limit set to hard limit (%s) on printer %s.") % (str(softlimit), printer.Name))
                        
            if options["add"] :    
                allentries = []    
                for name in names :
                    email = ""
                    if not options["groups"] :
                        splitname = name.split('/', 1)     # username/email
                        if len(splitname) == 1 :
                            splitname.append("")
                        (name, email) = splitname
                        if email and (email.count('@') != 1) :
                            self.logger.log_message(_("Invalid email address %s") % email)
                            email = ""
                    entry = getattr(self.storage, "get%s" % suffix)(name)
                    if email and not options["groups"] :
                        entry.Email = email
                    entrypquota = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer)
                    allentries.append((entry, entrypquota))
            else :   
                allentries = getattr(self.storage, "getPrinter%ssAndQuotas" % suffix)(printer, names)
                
            for (entry, entrypquota) in allentries :
                if not changed.has_key(entry.Name) :
                    changed[entry.Name] = {}
                    if not options["groups"] :
                        changed[entry.Name]["ingroups"] = []
                        
                if not entry.Exists :        
                    # not found
                    if options["add"] :
                        # In case we want to add something, it is crucial
                        # that we DON'T check with the system accounts files
                        # like /etc/passwd because users may be defined 
                        # only remotely
                        if self.isValidName(entry.Name) :
                            entry = getattr(self.storage, "add%s" % suffix)(entry)
                        else :    
                            if options["groups"] :
                                self.logger.log_message(_("Invalid group name %s") % entry.Name)
                            else :    
                                self.logger.log_message(_("Invalid user name %s") % entry.Name)
                elif options["delete"] :                
                    todelete[entry.Name] = entry
                                
                if entry.Exists and (not entrypquota.Exists) :
                    # not found
                    if options["add"] :
                        entrypquota = getattr(self.storage, "add%sPQuota" % suffix)(entry, printer)
                        
                if not entrypquota.Exists :     
                    self.logger.log_message(_("Quota not found for object %s on printer %s.") % (entry.Name, printer.Name))
                else :    
                    if options["noquota"] or options["prototype"] or ((softlimit is not None) and (hardlimit is not None)) :
                        entrypquota.setLimits(softlimit, hardlimit)
                    if limitby :
                        if changed[entry.Name].get("limitby") is None :
                            entry.setLimitBy(limitby)
                            changed[entry.Name]["limitby"] = limitby
                    
                    if not options["groups"] :
                        if options["reset"] :
                            entrypquota.reset()
                            
                        if balance :
                            if changed[entry.Name].get("balance") is None :
                                if balance.startswith("+") or balance.startswith("-") :
                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
                                    entry.setAccountBalance(newbalance, newlifetimepaid)
                                else :
                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
                                    entry.setAccountBalance(balancevalue, newlifetimepaid)
                                changed[entry.Name]["balance"] = balance
                                
                        for groupname in groupnames :        
                            # not executed if option --ingroups is not used
                            if groupname not in changed[entry.Name]["ingroups"] :
                                group = self.storage.getGroup(groupname)
                                if group.Exists :
                                    self.storage.addUserToGroup(entry, group)
                                    changed[entry.Name]["ingroups"].append(groupname)
                                else :
                                    self.logger.log_message(_("Group %s not found in the PyKota Storage.") % groupname)
                                    
                    # This line disabled to prevent sending of unwanted email                 
                    # messages if quota is reached at creation/modification time.
                    # The check will be done at print time anyway.
                    # getattr(self, "warn%sPQuota" % suffix)(entrypquota)    
                        
        # Now delete what has to be deleted                
        for (name, entry) in todelete.items() :                
            entry.delete()
                     
if __name__ == "__main__" : 
    retcode = 0
    try :
        defaults = { \
                     "printer" : "*", \
                   }
        short_options = "vhdc:l:b:i:naugrp:P:S:H:G:"
        long_options = ["help", "version", "charge=", "delete", "limitby=", "balance=", "ingroups=", "noquota", "add", "users", "groups", "reset", "prototype=", "printer=", "softlimit=", "hardlimit=", "pgroups="]
        
        # Initializes the command line tool
        editor = EdPyKota(doc=__doc__)
        
        # parse and checks the command line
        (options, args) = editor.parseCommandline(sys.argv[1:], short_options, long_options)
        
        # sets long options
        options["help"] = options["h"] or options["help"]
        options["version"] = options["v"] or options["version"]
        options["add"] = options["a"] or options["add"]
        options["users"] = options["u"] or options["users"]
        options["groups"] = options["g"] or options["groups"]
        options["prototype"] = options["p"] or options["prototype"]
        options["printer"] = options["P"] or options["printer"] or defaults["printer"]
        options["softlimit"] = options["S"] or options["softlimit"]
        options["hardlimit"] = options["H"] or options["hardlimit"] 
        options["reset"] = options["r"] or options["reset"] 
        options["noquota"] = options["n"] or options["noquota"]
        options["limitby"] = options["l"] or options["limitby"]
        options["balance"] = options["b"] or options["balance"] 
        options["delete"] = options["d"] or options["delete"] 
        options["ingroups"] = options["i"] or options["ingroups"]
        options["charge"] = options["c"] or options["charge"]
        options["pgroups"] = options["G"] or options["pgroups"]
        
        if options["help"] :
            editor.display_usage_and_quit()
        elif options["version"] :
            editor.display_version_and_quit()
        elif options["users"] and options["groups"] :    
            raise PyKotaToolError, _("incompatible options, see help.")
        elif (options["add"] or options["prototype"]) and options["delete"] :    
            raise PyKotaToolError, _("incompatible options, see help.")
        elif (options["softlimit"] or options["hardlimit"]) and options["prototype"] :    
            raise PyKotaToolError, _("incompatible options, see help.")
        elif options["noquota"] and (options["prototype"] or options["hardlimit"] or options["softlimit"]) :
            raise PyKotaToolError, _("incompatible options, see help.")
        elif options["groups"] and (options["balance"] or options["ingroups"]) :
            raise PyKotaToolError, _("incompatible options, see help.")
        else :
            retcode = editor.main(args, options)
    except SystemExit :        
        pass
    except :
        try :
            editor.crashed("edpykota failed")
        except :    
            pass
        retcode = -1

    try :
        editor.storage.close()
    except (TypeError, NameError, AttributeError) :    
        pass
        
    sys.exit(retcode)    
