root / pykota / trunk / pykota / dumper.py @ 2342

Revision 2342, 11.2 kB (checked in by jerome, 19 years ago)

The pkbcodes command line tool now works fine with the PostgreSQL
backend. The dumpykota command can now dump billing codes too.
Still no code for LDAP though.
NB : a database upgrade is necessary AGAIN !
Severity : no need to play with this until there's LDAP support.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota Print Quota Data Dumper
2#
3# PyKota - Print Quotas for CUPS and LPRng
4#
5# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19#
20# $Id$
21#
22#
23
24import sys
25import os
26import pwd
27from xml.sax import saxutils
28
29from mx import DateTime
30
31try :
32    import jaxml
33except ImportError :   
34    sys.stderr.write("The jaxml Python module is not installed. XML output is disabled.\n")
35    sys.stderr.write("Download jaxml from http://www.librelogiciel.com/software/ or from your Debian archive of choice\n")
36    hasJAXML = 0
37else :   
38    hasJAXML = 1
39
40from pykota import version
41from pykota.tool import PyKotaTool, PyKotaToolError, N_
42
43class DumPyKota(PyKotaTool) :       
44    """A class for dumpykota."""
45    validdatatypes = { "history" : N_("History"),
46                       "users" : N_("Users"),
47                       "groups" : N_("Groups"),
48                       "printers" : N_("Printers"),
49                       "upquotas" : N_("Users Print Quotas"),
50                       "gpquotas" : N_("Users Groups Print Quotas"),
51                       "payments" : N_("History of Payments"),
52                       "pmembers" : N_("Printers Groups Membership"), 
53                       "umembers" : N_("Users Groups Membership"),
54                       "billingcodes" : N_("Billing Codes"),
55                     }
56    validformats = { "csv" : N_("Comma Separated Values"),
57                     "ssv" : N_("Semicolon Separated Values"),
58                     "tsv" : N_("Tabulation Separated Values"),
59                     "xml" : N_("eXtensible Markup Language"),
60                     "cups" : N_("CUPS' page_log"),
61                   } 
62    validfilterkeys = [ "username",
63                        "groupname",
64                        "printername",
65                        "pgroupname",
66                        "hostname",
67                        "billingcode",
68                        "start",
69                        "end",
70                      ]
71    def main(self, arguments, options, restricted=1) :
72        """Print Quota Data Dumper."""
73        if restricted and not self.config.isAdmin :
74            raise PyKotaToolError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
75           
76        extractonly = {}
77        for filterexp in arguments :
78            if filterexp.strip() :
79                try :
80                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
81                    if filterkey not in self.validfilterkeys :
82                        raise ValueError               
83                except ValueError :   
84                    raise PyKotaToolError, _("Invalid filter value [%s], see help.") % filterexp
85                else :   
86                    extractonly.update({ filterkey : filtervalue })
87           
88        datatype = options["data"]
89        if datatype not in self.validdatatypes.keys() :
90            raise PyKotaToolError, _("Invalid modifier [%s] for --data command line option, see help.") % datatype
91                   
92        format = options["format"]
93        if (format not in self.validformats.keys()) \
94              or ((format == "cups") and ((datatype != "history") or options["sum"])) :
95            raise PyKotaToolError, _("Invalid modifier [%s] for --format command line option, see help.") % format
96           
97        if (format == "xml") and not hasJAXML :
98            raise PyKotaToolError, _("XML output is disabled because the jaxml module is not available.")
99           
100        if options["sum"] and datatype not in ("payments", "history") : 
101            raise PyKotaToolError, _("Invalid data type [%s] for --sum command line option, see help.") % datatype
102           
103        entries = getattr(self.storage, "extract%s" % datatype.title())(extractonly)
104        if entries :
105            mustclose = 0   
106            if options["output"].strip() == "-" :   
107                self.outfile = sys.stdout
108            else :   
109                self.outfile = open(options["output"], "w")
110                mustclose = 1
111               
112            retcode = getattr(self, "dump%s" % format.title())(self.summarizeDatas(entries, datatype, extractonly, options["sum"]), datatype)
113           
114            if mustclose :
115                self.outfile.close()
116               
117            return retcode   
118        return 0
119       
120    def summarizeDatas(self, entries, datatype, extractonly, sum=0) :   
121        """Transforms the datas into a summarized view (with totals).
122       
123           If sum is false, returns the entries unchanged.
124        """   
125        if not sum :
126            return entries
127        else :   
128            headers = entries[0]
129            nbheaders = len(headers)
130            fieldnumber = {}
131            fieldname = {}
132            for i in range(nbheaders) :
133                fieldnumber[headers[i]] = i
134           
135            if datatype == "payments" :
136                totalize = [ ("amount", float) ]
137                keys = [ "username" ]
138            else : # elif datatype == "history"
139                totalize = [ ("jobsize", int), 
140                             ("jobprice", float),
141                             ("jobsizebytes", int),
142                           ]
143                keys = [ k for k in ("username", "printername", "hostname", "billingcode") if k in extractonly.keys() ]
144               
145            newentries = [ headers ]
146            sortedentries = entries[1:]
147            if keys :
148                # If we have several keys, we can sort only on the first one, because they
149                # will vary the same way.
150                sortedentries.sort(lambda x, y, fnum=fieldnumber[keys[0]] : cmp(x[fnum], y[fnum]))
151            totals = {}
152            for (k, t) in totalize :
153                totals[k] = { "convert" : t, "value" : 0.0 }
154            prevkeys = {}
155            for k in keys :
156                prevkeys[k] = sortedentries[0][fieldnumber[k]]
157            for entry in sortedentries :
158                curval = '-'.join([str(entry[fieldnumber[k]]) for k in keys])
159                prevval = '-'.join([str(prevkeys[k]) for k in keys])
160                if curval != prevval :
161                    summary = [ "*" ] * nbheaders
162                    for k in keys :
163                        summary[fieldnumber[k]] = prevkeys[k]
164                    for k in totals.keys() :   
165                        summary[fieldnumber[k]] = totals[k]["convert"](totals[k]["value"])
166                    newentries.append(summary)
167                    for k in totals.keys() :   
168                        totals[k]["value"] = totals[k]["convert"](entry[fieldnumber[k]])
169                else :   
170                    for k in totals.keys() :   
171                        totals[k]["value"] += totals[k]["convert"](entry[fieldnumber[k]])
172                for k in keys :
173                    prevkeys[k] = entry[fieldnumber[k]]
174            summary = [ "*" ] * nbheaders
175            for k in keys :
176                summary[fieldnumber[k]] = prevkeys[k]
177            for k in totals.keys() :   
178                summary[fieldnumber[k]] = totals[k]["convert"](totals[k]["value"])
179            newentries.append(summary)
180            return newentries
181           
182    def dumpWithSeparator(self, separator, entries) :   
183        """Dumps datas with a separator."""
184        for entry in entries :
185            line = []
186            for value in entry :
187                if type(value).__name__ in ("str", "NoneType") :
188                    line.append('"%s"' % str(value).replace(separator, "\\%s" % separator).replace('"', '\\"'))
189                else :   
190                    line.append(str(value))
191            try :
192                self.outfile.write("%s\n" % separator.join(line))
193            except IOError, msg :   
194                sys.stderr.write("%s : %s\n" % (_("PyKota data dumper failed : I/O error"), msg))
195                return -1
196        return 0       
197       
198    def dumpCsv(self, entries, dummy) :   
199        """Dumps datas with a comma as the separator."""
200        return self.dumpWithSeparator(",", entries)
201                           
202    def dumpSsv(self, entries, dummy) :   
203        """Dumps datas with a comma as the separator."""
204        return self.dumpWithSeparator(";", entries)
205                           
206    def dumpTsv(self, entries, dummy) :   
207        """Dumps datas with a comma as the separator."""
208        return self.dumpWithSeparator("\t", entries)
209       
210    def dumpCups(self, entries, dummy) :   
211        """Dumps history datas as CUPS' page_log format."""
212        fieldnames = entries[0]
213        fields = {}
214        for i in range(len(fieldnames)) :
215            fields[fieldnames[i]] = i
216        sortindex = fields["jobdate"]   
217        entries = entries[1:]
218        entries.sort(lambda m,n,si=sortindex : cmp(m[si], n[si]))
219        for entry in entries :   
220            printername = entry[fields["printername"]]
221            username = entry[fields["username"]]
222            jobid = entry[fields["jobid"]]
223            jobdate = DateTime.ISO.ParseDateTime(entry[fields["jobdate"]])
224            gmtoffset = jobdate.gmtoffset()
225            jobdate = "%s %+03i00" % (jobdate.strftime("%d/%b/%Y:%H:%M:%S"), gmtoffset.hour)
226            jobsize = entry[fields["jobsize"]] or 0
227            copies = entry[fields["copies"]] or 1
228            hostname = entry[fields["hostname"]] or ""
229            billingcode = entry[fields["billingcode"]] or "-"
230            for pagenum in range(1, jobsize+1) :
231                self.outfile.write("%s %s %s [%s] %s %s %s %s\n" % (printername, username, jobid, jobdate, pagenum, copies, billingcode, hostname))
232       
233    def dumpXml(self, entries, datatype) :   
234        """Dumps datas as XML."""
235        x = jaxml.XML_document(encoding="UTF-8")
236        x.pykota(version=version.__version__, author=version.__author__)
237        x.dump(storage=self.config.getStorageBackend()["storagebackend"], type=datatype)
238        headers = entries[0]
239        for entry in entries[1:] :
240            x._push()
241            x.entry()
242            for (header, value) in zip(headers, entry) :
243                strvalue = str(value)
244                typval = type(value).__name__
245                if header in ("filename", "title", "options", "billingcode") \
246                          and (typval == "str") :
247                    try :
248                        strvalue = unicode(strvalue, self.getCharset()).encode("UTF-8")
249                    except UnicodeError :   
250                        pass
251                    strvalue = saxutils.escape(strvalue, { "'" : "&apos;", \
252                                                           '"' : "&quot;" })
253                x.attribute(strvalue, type=typval, name=header)
254            x._pop()   
255        x._output(self.outfile)
Note: See TracBrowser for help on using the browser.