Index: pykota/trunk/pykota/tool.py
===================================================================
--- pykota/trunk/pykota/tool.py (revision 2650)
+++ pykota/trunk/pykota/tool.py (revision 2657)
@@ -195,4 +195,10 @@
         return self.charset
         
+    def display(self, message) :
+        """Display a message but only if stdout is a tty."""
+        if sys.stdout.isatty() :
+            sys.stdout.write(message)
+            sys.stdout.flush()
+            
     def logdebug(self, message) :    
         """Logs something to debug output if debug is enabled."""
Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 2654)
+++ pykota/trunk/pykota/storages/sql.py (revision 2657)
@@ -203,9 +203,9 @@
             fields = result[0]
             user.ident = fields.get("id")
-            user.Name = self.databaseToUserCharset(fields.get("username", username))
             user.LimitBy = fields.get("limitby") or "quota"
             user.AccountBalance = fields.get("balance")
             user.LifeTimePaid = fields.get("lifetimepaid")
             user.Email = fields.get("email")
+            user.Description = self.databaseToUserCharset(fields.get("description"))
             user.OverCharge = fields.get("overcharge", 1.0)
             user.Exists = 1
@@ -220,8 +220,8 @@
             fields = result[0]
             group.ident = fields.get("id")
-            group.Name = self.databaseToUserCharset(fields.get("groupname", groupname))
             group.LimitBy = fields.get("limitby") or "quota"
             group.AccountBalance = fields.get("balance")
             group.LifeTimePaid = fields.get("lifetimepaid")
+            group.Description = self.databaseToUserCharset(fields.get("description"))
             group.Exists = 1
         return group
@@ -235,5 +235,4 @@
             fields = result[0]
             printer.ident = fields.get("id")
-            printer.Name = self.databaseToUserCharset(fields.get("printername", printername))
             printer.PricePerJob = fields.get("priceperjob") or 0.0
             printer.PricePerPage = fields.get("priceperpage") or 0.0
@@ -255,5 +254,4 @@
             fields = result[0]
             code.ident = fields.get("id")
-            code.BillingCode = self.databaseToUserCharset(fields.get("billingcode"))
             code.Description = self.databaseToUserCharset(fields.get("description") or "")
             code.Balance = fields.get("balance") or 0.0
@@ -375,7 +373,8 @@
         result = self.doSearch("SELECT * FROM printers")
         if result :
+            patterns = printerpattern.split(",")
             for record in result :
                 pname = self.databaseToUserCharset(record["printername"])
-                if self.tool.matchString(pname, printerpattern.split(",")) :
+                if self.tool.matchString(pname, patterns) :
                     printer = StoragePrinter(self, pname)
                     printer.ident = record.get("id")
@@ -394,4 +393,52 @@
         return printers        
         
+    def getMatchingUsers(self, userpattern) :
+        """Returns the list of all users for which name matches a certain pattern."""
+        users = []
+        # We 'could' do a SELECT username FROM users WHERE username LIKE ...
+        # but we don't because other storages semantics may be different, so every
+        # storage should use fnmatch to match patterns and be storage agnostic
+        result = self.doSearch("SELECT * FROM users")
+        if result :
+            patterns = userpattern.split(",")
+            for record in result :
+                uname = self.databaseToUserCharset(record["username"])
+                if self.tool.matchString(uname, patterns) :
+                    user = StorageUser(self, uname)
+                    user.ident = record.get("id")
+                    user.LimitBy = record.get("limitby") or "quota"
+                    user.AccountBalance = record.get("balance")
+                    user.LifeTimePaid = record.get("lifetimepaid")
+                    user.Email = record.get("email")
+                    user.Description = self.databaseToUserCharset(record.get("description"))
+                    user.OverCharge = record.get("overcharge", 1.0)
+                    user.Exists = 1
+                    users.append(user)
+                    self.cacheEntry("USERS", user.Name, user)
+        return users        
+        
+    def getMatchingGroups(self, grouppattern) :
+        """Returns the list of all groups for which name matches a certain pattern."""
+        groups = []
+        # We 'could' do a SELECT groupname FROM groups WHERE groupname LIKE ...
+        # but we don't because other storages semantics may be different, so every
+        # storage should use fnmatch to match patterns and be storage agnostic
+        result = self.doSearch("SELECT groups.*,COALESCE(SUM(balance), 0.0) AS balance, COALESCE(SUM(lifetimepaid), 0.0) AS lifetimepaid FROM groups LEFT OUTER JOIN users ON users.id IN (SELECT userid FROM groupsmembers WHERE groupid=groups.id) GROUP BY groups.id,groups.groupname,groups.limitby,groups.description")
+        if result :
+            patterns = grouppattern.split(",")
+            for record in result :
+                gname = self.databaseToUserCharset(record["groupname"])
+                if self.tool.matchString(gname, patterns) :
+                    group = StorageGroup(self, gname)
+                    group.ident = record.get("id")
+                    group.LimitBy = record.get("limitby") or "quota"
+                    group.AccountBalance = record.get("balance")
+                    group.LifeTimePaid = record.get("lifetimepaid")
+                    group.Description = self.databaseToUserCharset(record.get("description"))
+                    group.Exists = 1
+                    groups.append(group)
+                    self.cacheEntry("GROUPS", group.Name, group)
+        return groups        
+        
     def getMatchingBillingCodes(self, billingcodepattern) :
         """Returns the list of all billing codes for which the label matches a certain pattern."""
@@ -399,7 +446,8 @@
         result = self.doSearch("SELECT * FROM billingcodes")
         if result :
+            patterns = billingcodepattern.split(",")
             for record in result :
                 bcode = self.databaseToUserCharset(record["billingcode"])
-                if self.tool.matchString(bcode, billingcodepattern.split(",")) :
+                if self.tool.matchString(bcode, patterns) :
                     code = StorageBillingCode(self, bcode)
                     code.ident = record.get("id")
Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 2653)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 2657)
@@ -357,5 +357,4 @@
             fields = result[0][1]
             user.ident = result[0][0]
-            user.Name = fields.get("pykotaUserName", [self.databaseToUserCharset(username)])[0] 
             user.Email = fields.get(self.info["usermail"], [None])[0]
             user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
@@ -670,28 +669,110 @@
         printers = []
         # see comment at the same place in pgstorage.py
-        printerpattern = [self.userCharsetToDatabase(p) for p in printerpattern.split(",")]
-        result = self.doSearch("(&(objectClass=pykotaPrinter)(|%s))" % \
-                                  "".join(["(pykotaPrinterName=%s)(%s=%s)" % (pname, self.info["printerrdn"], pname) for pname in printerpattern]), \
+        result = self.doSearch("objectClass=pykotaPrinter", \
                                   ["pykotaPrinterName", "pykotaPricePerPage", "pykotaPricePerJob", "pykotaMaxJobSize", "pykotaPassThrough", "uniqueMember", "description"], \
                                   base=self.info["printerbase"])
         if result :
+            patterns = printerpattern.split(",")
             for (printerid, fields) in result :
                 printername = self.databaseToUserCharset(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0])
-                printer = StoragePrinter(self, printername)
-                printer.ident = printerid
-                printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
-                printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
-                printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
-                printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
-                if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
-                    printer.PassThrough = 1
-                else :
-                    printer.PassThrough = 0
-                printer.uniqueMember = fields.get("uniqueMember", [])
-                printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
-                printer.Exists = 1
-                printers.append(printer)
-                self.cacheEntry("PRINTERS", printer.Name, printer)
+                if self.tool.matchString(printername, patterns) :
+                    printer = StoragePrinter(self, printername)
+                    printer.ident = printerid
+                    printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
+                    printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
+                    printer.MaxJobSize = int(fields.get("pykotaMaxJobSize", [0])[0])
+                    printer.PassThrough = fields.get("pykotaPassThrough", [None])[0]
+                    if printer.PassThrough in (1, "1", "t", "true", "TRUE", "True") :
+                        printer.PassThrough = 1
+                    else :
+                        printer.PassThrough = 0
+                    printer.uniqueMember = fields.get("uniqueMember", [])
+                    printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    printer.Exists = 1
+                    printers.append(printer)
+                    self.cacheEntry("PRINTERS", printer.Name, printer)
         return printers        
+        
+    def getMatchingUsers(self, userpattern) :
+        """Returns the list of all users for which name matches a certain pattern."""
+        users = []
+        # see comment at the same place in pgstorage.py
+        result = self.doSearch("objectClass=pykotaAccount", \
+                                  ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "pykotaOverCharge"], \
+                                  base=self.info["userbase"])
+        if result :
+            patterns = userpattern.split(",")
+            for (userid, fields) in result :
+                username = self.databaseToUserCharset(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0])
+                if self.tool.matchString(username, patterns) :
+                    user = StorageUser(self, username)
+                    user.ident = userid
+                    user.Email = fields.get(self.info["usermail"], [None])[0]
+                    user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
+                    user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
+                    uname = self.userCharsetToDatabase(username)
+                    result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \
+                                              (uname, self.info["balancerdn"], uname), \
+                                              ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments"], \
+                                              base=self.info["balancebase"])
+                    if not result :
+                        raise PyKotaStorageError, _("No pykotaAccountBalance object found for user %s. Did you create LDAP entries manually ?") % username
+                    else :
+                        fields = result[0][1]
+                        user.idbalance = result[0][0]
+                        user.AccountBalance = fields.get("pykotaBalance")
+                        if user.AccountBalance is not None :
+                            if user.AccountBalance[0].upper() == "NONE" :
+                                user.AccountBalance = None
+                            else :    
+                                user.AccountBalance = float(user.AccountBalance[0])
+                        user.AccountBalance = user.AccountBalance or 0.0        
+                        user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
+                        if user.LifeTimePaid is not None :
+                            if user.LifeTimePaid[0].upper() == "NONE" :
+                                user.LifeTimePaid = None
+                            else :    
+                                user.LifeTimePaid = float(user.LifeTimePaid[0])
+                        user.LifeTimePaid = user.LifeTimePaid or 0.0        
+                        user.Payments = []
+                        for payment in fields.get("pykotaPayments", []) :
+                            try :
+                                (date, amount, description) = payment.split(" # ")
+                            except ValueError :
+                                # Payment with no description (old Payment)
+                                (date, amount) = payment.split(" # ")
+                                description = ""
+                            else :    
+                                description = self.databaseToUserCharset(base64.decodestring(description))
+                            user.Payments.append((date, float(amount), description))
+                    user.Exists = 1
+                    users.append(user)
+                    self.cacheEntry("USERS", user.Name, user)
+        return users       
+        
+    def getMatchingGroups(self, grouppattern) :
+        """Returns the list of all groups for which name matches a certain pattern."""
+        groups = []
+        # see comment at the same place in pgstorage.py
+        result = self.doSearch("objectClass=pykotaGroup", \
+                                  ["pykotaGroupName", "pykotaLimitBy"], \
+                                  base=self.info["groupbase"])
+        if result :
+            patterns = grouppattern.split(",")
+            for (groupid, fields) in result :
+                groupname = self.databaseToUserCharset(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0])
+                if self.tool.matchString(groupname, patterns) :
+                    group = StorageGroup(self, groupname)
+                    group.ident = groupid
+                    group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 
+                    group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
+                    group.AccountBalance = 0.0
+                    group.LifeTimePaid = 0.0
+                    for member in self.getGroupMembers(group) :
+                        if member.Exists :
+                            group.AccountBalance += member.AccountBalance
+                            group.LifeTimePaid += member.LifeTimePaid
+                    group.Exists = 1
+        return groups
         
     def getPrinterUsersAndQuotas(self, printer, names=["*"]) :        
@@ -1545,21 +1626,21 @@
         """Returns the list of all billing codes which match a certain pattern."""
         codes = []
-        billingcodepattern = [self.userCharsetToDatabase(b) for b in billingcodepattern.split(",")]
-        result = self.doSearch("(&(objectClass=pykotaBilling)(|%s))" % \
-                                "".join(["(pykotaBillingCode=%s)" % bcode for bcode in billingcodepattern]), \
+        result = self.doSearch("objectClass=pykotaBilling", \
                                 ["pykotaBillingCode", "description", "pykotaPageCounter", "pykotaBalance"], \
                                 base=self.info["billingcodebase"])
         if result :
+            patterns = billingcodepattern.split(",")
             for (codeid, fields) in result :
-                codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0])
-                code = StorageBillingCode(self, codename)
-                code.ident = codeid
-                code.BillingCode = codename
-                code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
-                code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
-                code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
-                code.Exists = 1
-                codes.append(code)
-                self.cacheEntry("BILLINGCODES", code.BillingCode, code)
+                bcode = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0])
+                if self.tool.matchString(bcode, patterns) :
+                    code = StorageBillingCode(self, codename)
+                    code.ident = codeid
+                    code.BillingCode = codename
+                    code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
+                    code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
+                    code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    code.Exists = 1
+                    codes.append(code)
+                    self.cacheEntry("BILLINGCODES", code.BillingCode, code)
         return codes        
         
Index: pykota/trunk/bin/edpykota
===================================================================
--- pykota/trunk/bin/edpykota (revision 2655)
+++ pykota/trunk/bin/edpykota (revision 2657)
@@ -253,300 +253,304 @@
         suffix = (options["groups"] and "Group") or "User"        
         
-        softlimit = hardlimit = None
-        
-        limitby = options["limitby"]
-        if limitby :
-            limitby = limitby.strip().lower()
-        if limitby :
-            if limitby not in ('quota', 'balance', 'noquota', \
-                                        'noprint', 'nochange') :
-                raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-            if limitby in ('noquota', 'nochange') :    
-                options["noquota"] = 1
-            if (limitby in ('nochange', 'noprint')) and options["groups"] :    
-                raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-
-        used = options["used"]
-        if used :
-            used = used.strip()
-            try :
-                int(used)
-            except ValueError :
-                raise PyKotaCommandLineError, _("Invalid used value %s.") % used
+        if options["delete"] :    
+            if not names :
+                raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
+            self.display("Processing...\n")
+            todelete = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
+            nbtotal = len(todelete)
+            for i in range(nbtotal) :
+                todelete[i].delete()
+                percent = 100.0 * float(i) / float(nbtotal)
+                self.display("\r%.02f%%" % percent)
+            self.display("\r100.00%%\r        ")
+            self.display("\r%s\n" % _("Done."))
+        else :
+            softlimit = hardlimit = None
+            
+            limitby = options["limitby"]
+            if limitby :
+                limitby = limitby.strip().lower()
+            if limitby :
+                if limitby not in ('quota', 'balance', 'noquota', \
+                                            'noprint', 'nochange') :
+                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
+                if limitby in ('noquota', 'nochange') :    
+                    options["noquota"] = 1
+                if (limitby in ('nochange', 'noprint')) and options["groups"] :    
+                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
+            
+            used = options["used"]
+            if used :
+                used = used.strip()
+                try :
+                    int(used)
+                except ValueError :
+                    raise PyKotaCommandLineError, _("Invalid used value %s.") % used
+                    
+            increase = options["increase"]
+            if increase :
+                try :
+                    increase = int(increase.strip())
+                except ValueError :
+                    raise PyKotaCommandLineError, _("Invalid increase value %s.") % increase
+            
+            if not options["noquota"] :
+                if options["softlimit"] :
+                    try :
+                        softlimit = int(options["softlimit"].strip())
+                        if softlimit < 0 :
+                            raise ValueError
+                    except ValueError :    
+                        raise PyKotaCommandLineError, _("Invalid softlimit value %s.") % options["softlimit"]
+                if options["hardlimit"] :
+                    try :
+                        hardlimit = int(options["hardlimit"].strip())
+                        if hardlimit < 0 :
+                            raise ValueError
+                    except ValueError :    
+                        raise PyKotaCommandLineError, _("Invalid hardlimit value %s.") % options["hardlimit"]
+                if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :        
+                    # error, exchange them
+                    self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
+                    (softlimit, hardlimit) = (hardlimit, softlimit)
                 
-        increase = options["increase"]
-        if increase :
-            try :
-                increase = int(increase.strip())
-            except ValueError :
-                raise PyKotaCommandLineError, _("Invalid increase value %s.") % increase
-
-        if not options["noquota"] :
-            if options["softlimit"] :
+            overcharge = options["overcharge"]
+            if overcharge :
                 try :
-                    softlimit = int(options["softlimit"].strip())
-                    if softlimit < 0 :
-                        raise ValueError
+                    overcharge = float(overcharge.strip())
+                except (ValueError, AttributeError) :    
+                    raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
+                    
+            balance = options["balance"]
+            if balance :
+                balance = balance.strip()
+                try :
+                    balancevalue = float(balance)
                 except ValueError :    
-                    raise PyKotaCommandLineError, _("Invalid softlimit value %s.") % options["softlimit"]
-            if options["hardlimit"] :
-                try :
-                    hardlimit = int(options["hardlimit"].strip())
-                    if hardlimit < 0 :
-                        raise ValueError
-                except ValueError :    
-                    raise PyKotaCommandLineError, _("Invalid hardlimit value %s.") % options["hardlimit"]
-            if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :        
-                # error, exchange them
-                self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
-                (softlimit, hardlimit) = (hardlimit, softlimit)
-            
-        overcharge = options["overcharge"]
-        if overcharge :
-            try :
-                overcharge = float(overcharge.strip())
-            except (ValueError, AttributeError) :    
-                raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
-                
-        balance = options["balance"]
-        if balance :
-            balance = balance.strip()
-            try :
-                balancevalue = float(balance)
-            except ValueError :    
-                raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
-            
-        if options["charge"] :
-            try :
-                charges = [float(part) for part in options["charge"].split(',', 1)]
-            except ValueError :    
-                raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
-            else :    
-                if len(charges) > 2 :
-                    charges = charges[:2]
-                if len(charges) != 2 :
-                    charges = [charges[0], None]
-                    
-        if options["ingroups"] :    
-            groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
-        else :    
-            groupnames = []
-            
-        rejectunknown = self.config.getRejectUnknown()    
-        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 PyKotaCommandLineError, _("Invalid printer name %s") % pname
-            else :
-                raise PyKotaCommandLineError, _("There's no printer matching %s") % pname
-        if not names :    
-            if options["delete"] :    
-                raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
-            else :
-                names = getattr(self.storage, "getAll%ssNames" % suffix)() # all users or groups
-                
-        printersgroups = []        
-        if options["pgroups"] :        
-            printersgroups = self.storage.getMatchingPrinters(options["pgroups"])
-            
-        if options["prototype"] :    
-            protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
-            if not protoentry.Exists :
-                raise PyKotaCommandLineError, _("Prototype object %s not found in Quota Storage.") % protoentry.Name
-            else :    
-                limitby = protoentry.LimitBy
-                balancevalue = protoentry.AccountBalance
-                if balancevalue is not None :
-                    balance = str(abs(balancevalue))
-                else :    
-                    balance = None
-                overcharge = getattr(protoentry, "OverCharge", None)
-            
-        sys.stdout.write(_("Managing print quotas... (this may take a lot of time)"))
-        sys.stdout.flush()
-        missingusers = {}
-        missinggroups = {}    
-        todelete = {}    
-        changed = {} # tracks changes made at the user/group level
-        for printer in printers :
-            for pgroup in printersgroups :
-                pgroup.addPrinterToGroup(printer)    
+                    raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
                 
             if options["charge"] :
-                (perpage, perjob) = charges
-                printer.setPrices(perpage, perjob)    
+                try :
+                    charges = [float(part) for part in options["charge"].split(',', 1)]
+                except ValueError :    
+                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
+                else :    
+                    if len(charges) > 2 :
+                        charges = charges[:2]
+                    if len(charges) != 2 :
+                        charges = [charges[0], None]
+                        
+            if options["ingroups"] :    
+                groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
+            else :    
+                groupnames = []
                 
-            if options["prototype"] :
-                protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
-                if not protoquota.Exists :
-                    self.printInfo(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
+            rejectunknown = self.config.getRejectUnknown()    
+            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 PyKotaCommandLineError, _("Invalid printer name %s") % pname
+                else :
+                    raise PyKotaCommandLineError, _("There's no printer matching %s") % pname
+            if not names :    
+                names = getattr(self.storage, "getAll%ssNames" % suffix)() # all users or groups
+                    
+            printersgroups = []        
+            if options["pgroups"] :        
+                printersgroups = self.storage.getMatchingPrinters(options["pgroups"])
+                
+            if options["prototype"] :    
+                protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
+                if not protoentry.Exists :
+                    raise PyKotaCommandLineError, _("Prototype object %s not found in Quota Storage.") % protoentry.Name
                 else :    
-                    (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
+                    limitby = protoentry.LimitBy
+                    balancevalue = protoentry.AccountBalance
+                    if balancevalue is not None :
+                        balance = str(abs(balancevalue))
+                    else :    
+                        balance = None
+                    overcharge = getattr(protoentry, "OverCharge", None)
+                
+            sys.stdout.write(_("Managing print quotas... (this may take a lot of time)"))
+            sys.stdout.flush()
+            missingusers = {}
+            missinggroups = {}    
+            changed = {} # tracks changes made at the user/group level
+            for printer in printers :
+                for pgroup in printersgroups :
+                    pgroup.addPrinterToGroup(printer)    
                     
-            if not options["noquota"] :    
-                if hardlimit is None :    
-                    hardlimit = softlimit
-                    if hardlimit is not None :
-                        self.printInfo(_("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.printInfo(_("Undefined soft limit set to hard limit (%s) on printer %s.") % (str(softlimit), printer.Name))
+                if options["charge"] :
+                    (perpage, perjob) = charges
+                    printer.setPrices(perpage, perjob)    
+                    
+                if options["prototype"] :
+                    protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
+                    if not protoquota.Exists :
+                        self.printInfo(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
+                    else :    
+                        (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
                         
-            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.printInfo(_("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)
-                
-            # TODO : do this only once !!!    
-            allnames = [entry.Name for (entry, dummy) in allentries]
-            for name in names :    
-                if not self.matchString(name, allnames) :
-                    if options["groups"] :
-                        missinggroups[name] = 1
+                if not options["noquota"] :    
+                    if hardlimit is None :    
+                        hardlimit = softlimit
+                        if hardlimit is not None :
+                            self.printInfo(_("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.printInfo(_("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.printInfo(_("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)
+                    
+                # TODO : do this only once !!!    
+                allnames = [entry.Name for (entry, dummy) in allentries]
+                for name in names :    
+                    if not self.matchString(name, allnames) :
+                        if options["groups"] :
+                            missinggroups[name] = 1
+                        else :    
+                            missingusers[name] = 1
+                    
+                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) :
+                                reject = 0
+                                if rejectunknown :
+                                    if options["groups"] :
+                                        try :
+                                            grp.getgrnam(entry.Name)
+                                        except KeyError :    
+                                            self.printInfo(_("Unknown group %s") % entry.Name, "error")
+                                            reject = 1
+                                    else :    
+                                        try :
+                                            pwd.getpwnam(entry.Name)
+                                        except KeyError :    
+                                            self.printInfo(_("Unknown user %s") % entry.Name, "error")
+                                            reject = 1
+                                if not reject :        
+                                    entry = getattr(self.storage, "add%s" % suffix)(entry)
+                            else :    
+                                if options["groups"] :
+                                    self.printInfo(_("Invalid group name %s") % entry.Name)
+                                else :    
+                                    self.printInfo(_("Invalid user name %s") % entry.Name)
+                        else :
+                            if options["groups"] :
+                                missinggroups[entry.Name] = 1
+                            else :    
+                                missingusers[entry.Name] = 1
+                                    
+                    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.printInfo(_("Quota not found for object %s on printer %s.") % (entry.Name, printer.Name))
                     else :    
-                        missingusers[name] = 1
-                
-            for (entry, entrypquota) in allentries :
-                if not changed.has_key(entry.Name) :
-                    changed[entry.Name] = {}
-                    if not options["groups"] :
-                        changed[entry.Name]["ingroups"] = []
+                        if options["noquota"] or options["prototype"] \
+                           or ((softlimit is not None) and (hardlimit is not None)) :
+                            entrypquota.setLimits(softlimit, hardlimit)
+                        if increase :
+                           if (entrypquota.SoftLimit is None) \
+                               or (entrypquota.HardLimit is None) :
+                               self.printInfo(_("You can't increase limits by %s when no limit is set.") % increase, "error")
+                           else :
+                               newsoft = entrypquota.SoftLimit + increase         
+                               newhard = entrypquota.HardLimit + increase         
+                               if (newsoft >= 0) and (newhard >= 0) :
+                                   entrypquota.setLimits(newsoft, newhard)
+                               else :    
+                                   self.printInfo(_("You can't set negative limits."), "error")
+                        if limitby :
+                            if changed[entry.Name].get("limitby") is None :
+                                entry.setLimitBy(limitby)
+                                changed[entry.Name]["limitby"] = limitby
                         
-                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) :
-                            reject = 0
-                            if rejectunknown :
-                                if options["groups"] :
-                                    try :
-                                        grp.getgrnam(entry.Name)
-                                    except KeyError :    
-                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
-                                        reject = 1
-                                else :    
-                                    try :
-                                        pwd.getpwnam(entry.Name)
-                                    except KeyError :    
-                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
-                                        reject = 1
-                            if not reject :        
-                                entry = getattr(self.storage, "add%s" % suffix)(entry)
-                        else :    
-                            if options["groups"] :
-                                self.printInfo(_("Invalid group name %s") % entry.Name)
-                            else :    
-                                self.printInfo(_("Invalid user name %s") % entry.Name)
-                    else :
-                        if options["groups"] :
-                            missinggroups[entry.Name] = 1
-                        else :    
-                            missingusers[entry.Name] = 1
-                elif options["delete"] :                
-                    todelete[entry.Name] = entry
+                        if options["reset"] :
+                            entrypquota.reset()
+                            
+                        if options["hardreset"] :    
+                            entrypquota.hardreset()
+                            
+                        if not options["groups"] :
+                            if used :
+                                entrypquota.setUsage(used)
                                 
-                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.printInfo(_("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 increase :
-                       if (entrypquota.SoftLimit is None) \
-                           or (entrypquota.HardLimit is None) :
-                           self.printInfo(_("You can't increase limits by %s when no limit is set.") % increase, "error")
-                       else :
-                           newsoft = entrypquota.SoftLimit + increase         
-                           newhard = entrypquota.HardLimit + increase         
-                           if (newsoft >= 0) and (newhard >= 0) :
-                               entrypquota.setLimits(newsoft, newhard)
-                           else :    
-                               self.printInfo(_("You can't set negative limits."), "error")
-                    if limitby :
-                        if changed[entry.Name].get("limitby") is None :
-                            entry.setLimitBy(limitby)
-                            changed[entry.Name]["limitby"] = limitby
-                    
-                    if options["reset"] :
-                        entrypquota.reset()
-                        
-                    if options["hardreset"] :    
-                        entrypquota.hardreset()
-                        
-                    if not options["groups"] :
-                        if used :
-                            entrypquota.setUsage(used)
+                            if overcharge is not None :    
+                                if changed[entry.Name].get("overcharge") is None :
+                                    entry.setOverChargeFactor(overcharge)
+                                    changed[entry.Name]["overcharge"] = overcharge
+                                    
+                            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, options["comment"])
+                                    else :
+                                        diff = balancevalue - float(entry.AccountBalance or 0.0)
+                                        newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
+                                        entry.setAccountBalance(balancevalue, newlifetimepaid, options["comment"])
+                                    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.printInfo(_("Group %s not found in the PyKota Storage.") % groupname)
                             
-                        if overcharge is not None :    
-                            if changed[entry.Name].get("overcharge") is None :
-                                entry.setOverChargeFactor(overcharge)
-                                changed[entry.Name]["overcharge"] = overcharge
-                                
-                        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, options["comment"])
-                                else :
-                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
-                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
-                                    entry.setAccountBalance(balancevalue, newlifetimepaid, options["comment"])
-                                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.printInfo(_("Group %s not found in the PyKota Storage.") % groupname)
-                        
-        # Now outputs the list of nonexistent users and groups                
-        for name in missingusers.keys() :
-            self.printInfo(_("Nonexistent user %s or missing print quota entry.") % name, level="warn")
-        for name in missinggroups.keys() :
-            self.printInfo(_("Nonexistent group %s or missing print quota entry.") % name, level="warn")
-        
-        # Now delete what has to be deleted                
-        for (name, entry) in todelete.items() :                
-            entry.delete()
-        sys.stdout.write("\nDone.\n")    
+            # Now outputs the list of nonexistent users and groups                
+            for name in missingusers.keys() :
+                self.printInfo(_("Nonexistent user %s or missing print quota entry.") % name, level="warn")
+            for name in missinggroups.keys() :
+                self.printInfo(_("Nonexistent group %s or missing print quota entry.") % name, level="warn")
+            
+            sys.stdout.write("\nDone.\n")    
                      
 if __name__ == "__main__" : 
Index: pykota/trunk/bin/pkbcodes
===================================================================
--- pykota/trunk/bin/pkbcodes (revision 2622)
+++ pykota/trunk/bin/pkbcodes (revision 2657)
@@ -89,44 +89,53 @@
             raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
             
-        if (options["list"] or options["reset"]) and not names :
-            names = ["*"]
-            
-        if options["add"] :    
-            billingcodes = []
-            for bname in names :
-                billingcode = self.storage.getBillingCode(bname)
-                if billingcode.Exists :
-                    if options["skipexisting"] :
-                        self.printInfo(_("Billing code [%s] already exists, skipping.") % billingcode.BillingCode)
-                    else :    
-                        self.printInfo(_("Billing code [%s] already exists, will be modified.") % billingcode.BillingCode)
-                        billingcodes.append(billingcode)
-                else :
-                    billingcode = self.storage.addBillingCode(bname)
-                    if not billingcode.Exists :
-                        raise PyKotaToolError, _("Impossible to add billingcode %s") % bname
-                    else :     
-                        billingcodes.append(billingcode)
-        else :        
-            billingcodes = self.storage.getMatchingBillingCodes(",".join(names))
-            if not billingcodes :
-                raise PyKotaCommandLineError, _("There's no billingcode matching %s") % " ".join(names)
-                    
-        for billingcode in billingcodes :        
-            if options["delete"] :
-                billingcode.delete()
-            elif options["list"] :    
-                print "%s [%s] %s %s %s %.2f %s" % \
-                      (billingcode.BillingCode, billingcode.Description, \
-                       billingcode.PageCounter, \
-                       _("pages"), \
-                       _("and"), \
-                       billingcode.Balance, \
-                       _("credits"))
-            else :    
-                if options["reset"] :
-                    billingcode.reset()    
-                if options["description"] is not None :
-                    billingcode.setDescription(options["description"].strip())
+        if options["delete"] :    
+            self.display("Processing...\n")
+            todelete = self.storage.getMatchingBillingCodes(",".join(names))
+            nbtotal = len(todelete)
+            for i in range(nbtotal) :
+                todelete[i].delete()
+                percent = 100.0 * float(i) / float(nbtotal)
+                self.display("\r%.02f%%" % percent)
+            self.display("\r100.00%%\r        ")
+            self.display("\r%s\n" % _("Done."))
+        else :
+            if (options["list"] or options["reset"]) and not names :
+                names = ["*"]
+                
+            if options["add"] :    
+                billingcodes = []
+                for bname in names :
+                    billingcode = self.storage.getBillingCode(bname)
+                    if billingcode.Exists :
+                        if options["skipexisting"] :
+                            self.printInfo(_("Billing code [%s] already exists, skipping.") % billingcode.BillingCode)
+                        else :    
+                            self.printInfo(_("Billing code [%s] already exists, will be modified.") % billingcode.BillingCode)
+                            billingcodes.append(billingcode)
+                    else :
+                        billingcode = self.storage.addBillingCode(bname)
+                        if not billingcode.Exists :
+                            raise PyKotaToolError, _("Impossible to add billingcode %s") % bname
+                        else :     
+                            billingcodes.append(billingcode)
+            else :        
+                billingcodes = self.storage.getMatchingBillingCodes(",".join(names))
+                if not billingcodes :
+                    raise PyKotaCommandLineError, _("There's no billingcode matching %s") % " ".join(names)
+                        
+            for billingcode in billingcodes :        
+                if options["list"] :    
+                    print "%s [%s] %s %s %s %.2f %s" % \
+                          (billingcode.BillingCode, billingcode.Description, \
+                           billingcode.PageCounter, \
+                           _("pages"), \
+                           _("and"), \
+                           billingcode.Balance, \
+                           _("credits"))
+                else :    
+                    if options["reset"] :
+                        billingcode.reset()    
+                    if options["description"] is not None :
+                        billingcode.setDescription(options["description"].strip())
                      
 if __name__ == "__main__" : 
Index: pykota/trunk/bin/pkprinters
===================================================================
--- pykota/trunk/bin/pkprinters (revision 2622)
+++ pykota/trunk/bin/pkprinters (revision 2657)
@@ -129,90 +129,99 @@
             raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
             
-        if options["list"] and not names :
-            names = ["*"]
-            
-        if options["groups"] :        
-            printersgroups = self.storage.getMatchingPrinters(options["groups"])
-            if not printersgroups :
-                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
-            
-        if options["charge"] :
-            try :
-                charges = [float(part) for part in options["charge"].split(',', 1)]
-            except ValueError :    
-                raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
-            else :    
-                if len(charges) > 2 :
-                    charges = charges[:2]
-                if len(charges) != 2 :
-                    charges = [charges[0], None]
-                (perpage, perjob) = charges
+        if options["delete"] :    
+            self.display("Processing...\n")
+            todelete = self.storage.getMatchingPrinters(",".join(names))
+            nbtotal = len(todelete)
+            for i in range(nbtotal) :
+                todelete[i].delete()
+                percent = 100.0 * float(i) / float(nbtotal)
+                self.display("\r%.02f%%" % percent)
+            self.display("\r100.00%%\r        ")
+            self.display("\r%s\n" % _("Done."))
+        else :
+            if options["list"] and not names :
+                names = ["*"]
                 
-        if options["maxjobsize"] :        
-            try :
-                maxjobsize = int(options["maxjobsize"])
-                if maxjobsize < 0 :
-                    raise ValueError
-            except ValueError :    
-                raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
-        else :        
-            maxjobsize = None
+            if options["groups"] :        
+                printersgroups = self.storage.getMatchingPrinters(options["groups"])
+                if not printersgroups :
+                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
                 
-                
-        if options["add"] :    
-            printers = []
-            for pname in names :
-                printer = self.storage.getPrinter(pname)
-                if printer.Exists :
-                    if options["skipexisting"] :
-                        self.printInfo(_("Printer %s already exists, skipping.") % printer.Name)
-                    else :    
-                        self.printInfo(_("Printer %s already exists, will be modified.") % printer.Name)
-                        printers.append(printer)
-                else :
-                    if self.isValidName(pname) :
-                        printer = self.storage.addPrinter(pname)
-                        if not printer.Exists :
-                            raise PyKotaToolError, _("Impossible to add printer %s") % pname
+            if options["charge"] :
+                try :
+                    charges = [float(part) for part in options["charge"].split(',', 1)]
+                except ValueError :    
+                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
+                else :    
+                    if len(charges) > 2 :
+                        charges = charges[:2]
+                    if len(charges) != 2 :
+                        charges = [charges[0], None]
+                    (perpage, perjob) = charges
+                    
+            if options["maxjobsize"] :        
+                try :
+                    maxjobsize = int(options["maxjobsize"])
+                    if maxjobsize < 0 :
+                        raise ValueError
+                except ValueError :    
+                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
+            else :        
+                maxjobsize = None
+                    
+                    
+            if options["add"] :    
+                printers = []
+                for pname in names :
+                    printer = self.storage.getPrinter(pname)
+                    if printer.Exists :
+                        if options["skipexisting"] :
+                            self.printInfo(_("Printer %s already exists, skipping.") % printer.Name)
                         else :    
+                            self.printInfo(_("Printer %s already exists, will be modified.") % printer.Name)
                             printers.append(printer)
-                    else :    
-                        raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
-        else :        
-            printers = self.storage.getMatchingPrinters(",".join(names))
-            if not printers :
-                raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
-                    
-        for printer in printers :        
-            if options["delete"] :
-                printer.delete()
-            elif options["list"] :    
-                parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
-                if parents : 
-                    parents = "%s %s" % (_("in"), parents)
-                print "%s [%s] (%s + #*%s)" % \
-                      (printer.Name, printer.Description, printer.PricePerJob, \
-                       printer.PricePerPage)
-                print "    %s" % (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF")))
-                print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
-                if parents :       
-                    print "    %s" % parents
-            else :    
-                if options["charge"] :
-                    printer.setPrices(perpage, perjob)    
-                if options["description"] is not None :
-                    printer.setDescription(options["description"].strip())
-                if options["nopassthrough"] and printer.PassThrough :    
-                    self.storage.setPrinterPassThroughMode(printer, 0)
-                if options["passthrough"] and not printer.PassThrough :    
-                    self.storage.setPrinterPassThroughMode(printer, 1)
-                if (maxjobsize is not None) and (printer.MaxJobSize != maxjobsize) :    
-                    self.storage.setPrinterMaxJobSize(printer, maxjobsize)
-                if options["groups"] :    
-                    for pgroup in printersgroups :
-                        if options["remove"] :
-                            pgroup.delPrinterFromGroup(printer)
-                        else :
-                            pgroup.addPrinterToGroup(printer)    
+                    else :
+                        if self.isValidName(pname) :
+                            printer = self.storage.addPrinter(pname)
+                            if not printer.Exists :
+                                raise PyKotaToolError, _("Impossible to add printer %s") % pname
+                            else :    
+                                printers.append(printer)
+                        else :    
+                            raise PyKotaCommandLineError, _("Invalid printer name %s") % pname
+            else :        
+                printers = self.storage.getMatchingPrinters(",".join(names))
+                if not printers :
+                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
+                        
+            for printer in printers :        
+                if options["list"] :    
+                    parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
+                    if parents : 
+                        parents = "%s %s" % (_("in"), parents)
+                    print "%s [%s] (%s + #*%s)" % \
+                          (printer.Name, printer.Description, printer.PricePerJob, \
+                           printer.PricePerPage)
+                    print "    %s" % (_("Passthrough mode : %s") % ((printer.PassThrough and _("ON")) or _("OFF")))
+                    print "    %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited")))
+                    if parents :       
+                        print "    %s" % parents
+                else :    
+                    if options["charge"] :
+                        printer.setPrices(perpage, perjob)    
+                    if options["description"] is not None :
+                        printer.setDescription(options["description"].strip())
+                    if options["nopassthrough"] and printer.PassThrough :    
+                        self.storage.setPrinterPassThroughMode(printer, 0)
+                    if options["passthrough"] and not printer.PassThrough :    
+                        self.storage.setPrinterPassThroughMode(printer, 1)
+                    if (maxjobsize is not None) and (printer.MaxJobSize != maxjobsize) :    
+                        self.storage.setPrinterMaxJobSize(printer, maxjobsize)
+                    if options["groups"] :    
+                        for pgroup in printersgroups :
+                            if options["remove"] :
+                                pgroup.delPrinterFromGroup(printer)
+                            else :
+                                pgroup.addPrinterToGroup(printer)    
                      
 if __name__ == "__main__" : 
