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

Revision 3050, 13.2 kB (checked in by jerome, 18 years ago)

Fixed date and time parsing, although I was unable to reproduce the problem reported...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[2016]1# PyKota Print Quota Data Dumper
2#
3# PyKota - Print Quotas for CUPS and LPRng
4#
[2622]5# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
[2016]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
[2302]18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
[2016]19#
20# $Id$
21#
[2034]22#
[2016]23
24import sys
25import os
26import pwd
[2264]27from xml.sax import saxutils
28
[2016]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
[2512]41from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, N_
[2016]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"),
[2342]54                       "billingcodes" : N_("Billing Codes"),
[2600]55                       "all": N_("All"),
[2016]56                     }
57    validformats = { "csv" : N_("Comma Separated Values"),
58                     "ssv" : N_("Semicolon Separated Values"),
59                     "tsv" : N_("Tabulation Separated Values"),
60                     "xml" : N_("eXtensible Markup Language"),
61                     "cups" : N_("CUPS' page_log"),
62                   } 
63    validfilterkeys = [ "username",
64                        "groupname",
65                        "printername",
66                        "pgroupname",
[2218]67                        "hostname",
68                        "billingcode",
[2266]69                        "start",
70                        "end",
[2016]71                      ]
[2032]72    def main(self, arguments, options, restricted=1) :
[2016]73        """Print Quota Data Dumper."""
[2032]74        if restricted and not self.config.isAdmin :
[2512]75            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
[2016]76           
77        datatype = options["data"]
78        if datatype not in self.validdatatypes.keys() :
[2512]79            raise PyKotaCommandLineError, _("Invalid modifier [%s] for --data command line option, see help.") % datatype
[2016]80                   
[2600]81        extractonly = {}
82        if datatype == "all" :           
83            if (options["format"] != "xml") or options["sum"] or arguments :
84                self.printInfo(_("Dumping all PyKota's datas forces format to XML, and disables --sum and filters."), "warn")
85            options["format"] = "xml"
86            options["sum"] = None
87        else :   
88            for filterexp in arguments :
89                if filterexp.strip() :
90                    try :
91                        (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
[3030]92                        filterkey = filterkey.lower()
[2600]93                        if filterkey not in self.validfilterkeys :
94                            raise ValueError               
95                    except ValueError :   
96                        raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
97                    else :   
98                        extractonly.update({ filterkey : filtervalue })
99           
[2016]100        format = options["format"]
[2567]101        if (format not in self.validformats.keys()) \
102           or ((format == "cups") \
103              and ((datatype != "history") or options["sum"])) :
[2512]104            raise PyKotaCommandLineError, _("Invalid modifier [%s] for --format command line option, see help.") % format
[2016]105           
106        if (format == "xml") and not hasJAXML :
107            raise PyKotaToolError, _("XML output is disabled because the jaxml module is not available.")
108           
[2285]109        if options["sum"] and datatype not in ("payments", "history") : 
[2512]110            raise PyKotaCommandLineError, _("Invalid data type [%s] for --sum command line option, see help.") % datatype
[2285]111           
[2600]112           
113        retcode = 0
114        nbentries = 0   
115        mustclose = 0   
116        if options["output"].strip() == "-" :   
117            self.outfile = sys.stdout
118        else :   
119            self.outfile = open(options["output"], "w")
120            mustclose = 1
121           
122        if datatype == "all" :   
123            # NB : order does matter to allow easier or faster restore
124            allentries = []
125            datatypes = [ "printers", "pmembers", "users", "groups", \
126                          "billingcodes", "umembers", "upquotas", \
127                          "gpquotas", "payments", "history" ]
[2602]128            neededdatatypes = datatypes[:]             
[2600]129            for datatype in datatypes :
130                entries = getattr(self.storage, "extract%s" % datatype.title())(extractonly)
[2601]131                if entries :
132                    nbentries += len(entries)
133                    allentries.append(entries)
[2602]134                else :   
135                    neededdatatypes.remove(datatype)
136            retcode = self.dumpXml(allentries, neededdatatypes)
[2600]137        else :   
138            entries = getattr(self.storage, "extract%s" % datatype.title())(extractonly)
139            if entries :
140                nbentries = len(entries)
141                retcode = getattr(self, "dump%s" % format.title())([self.summarizeDatas(entries, datatype, extractonly, options["sum"])], [datatype])
[2016]142               
[2600]143        if mustclose :
144            self.outfile.close()
145            if not nbentries : 
146                os.remove(options["output"])
[2016]147           
[2600]148        return retcode
[2016]149       
[2295]150    def summarizeDatas(self, entries, datatype, extractonly, sum=0) :   
[2285]151        """Transforms the datas into a summarized view (with totals).
152       
153           If sum is false, returns the entries unchanged.
154        """   
155        if not sum :
156            return entries
157        else :   
[2289]158            headers = entries[0]
159            nbheaders = len(headers)
160            fieldnumber = {}
[2293]161            fieldname = {}
[2289]162            for i in range(nbheaders) :
[2295]163                fieldnumber[headers[i]] = i
164           
[2289]165            if datatype == "payments" :
[2293]166                totalize = [ ("amount", float) ]
[2295]167                keys = [ "username" ]
168            else : # elif datatype == "history"
169                totalize = [ ("jobsize", int), 
170                             ("jobprice", float),
171                             ("jobsizebytes", int),
[2664]172                             ("precomputedjobsize", int),
173                             ("precomputedjobprice", float),
[2295]174                           ]
175                keys = [ k for k in ("username", "printername", "hostname", "billingcode") if k in extractonly.keys() ]
[2293]176               
[2295]177            newentries = [ headers ]
178            sortedentries = entries[1:]
179            if keys :
180                # If we have several keys, we can sort only on the first one, because they
181                # will vary the same way.
182                sortedentries.sort(lambda x, y, fnum=fieldnumber[keys[0]] : cmp(x[fnum], y[fnum]))
183            totals = {}
184            for (k, t) in totalize :
185                totals[k] = { "convert" : t, "value" : 0.0 }
186            prevkeys = {}
187            for k in keys :
188                prevkeys[k] = sortedentries[0][fieldnumber[k]]
189            for entry in sortedentries :
190                curval = '-'.join([str(entry[fieldnumber[k]]) for k in keys])
191                prevval = '-'.join([str(prevkeys[k]) for k in keys])
192                if curval != prevval :
193                    summary = [ "*" ] * nbheaders
194                    for k in keys :
195                        summary[fieldnumber[k]] = prevkeys[k]
196                    for k in totals.keys() :   
197                        summary[fieldnumber[k]] = totals[k]["convert"](totals[k]["value"])
198                    newentries.append(summary)
199                    for k in totals.keys() :   
200                        totals[k]["value"] = totals[k]["convert"](entry[fieldnumber[k]])
201                else :   
202                    for k in totals.keys() :   
[2664]203                        totals[k]["value"] += totals[k]["convert"](entry[fieldnumber[k]] or 0.0)
[2295]204                for k in keys :
205                    prevkeys[k] = entry[fieldnumber[k]]
206            summary = [ "*" ] * nbheaders
207            for k in keys :
208                summary[fieldnumber[k]] = prevkeys[k]
209            for k in totals.keys() :   
210                summary[fieldnumber[k]] = totals[k]["convert"](totals[k]["value"])
211            newentries.append(summary)
[2289]212            return newentries
[2285]213           
[2600]214    def dumpWithSeparator(self, separator, allentries) :   
[2016]215        """Dumps datas with a separator."""
[2600]216        for entries in allentries :
217            for entry in entries :
218                line = []
219                for value in entry :
220                    if type(value).__name__ in ("str", "NoneType") :
221                        line.append('"%s"' % str(value).replace(separator, "\\%s" % separator).replace('"', '\\"'))
222                    else :   
223                        line.append(str(value))
224                try :
225                    self.outfile.write("%s\n" % separator.join(line))
226                except IOError, msg :   
227                    sys.stderr.write("%s : %s\n" % (_("PyKota data dumper failed : I/O error"), msg))
228                    return -1
[2016]229        return 0       
230       
[2600]231    def dumpCsv(self, allentries, dummy) :   
[2016]232        """Dumps datas with a comma as the separator."""
[2600]233        return self.dumpWithSeparator(",", allentries)
[2016]234                           
[2600]235    def dumpSsv(self, allentries, dummy) :   
[2016]236        """Dumps datas with a comma as the separator."""
[2600]237        return self.dumpWithSeparator(";", allentries)
[2016]238                           
[2600]239    def dumpTsv(self, allentries, dummy) :   
[2016]240        """Dumps datas with a comma as the separator."""
[2600]241        return self.dumpWithSeparator("\t", allentries)
[2016]242       
[2600]243    def dumpCups(self, allentries, dummy) :   
[2016]244        """Dumps history datas as CUPS' page_log format."""
[2600]245        entries = allentries[0]
[2016]246        fieldnames = entries[0]
247        fields = {}
248        for i in range(len(fieldnames)) :
249            fields[fieldnames[i]] = i
250        sortindex = fields["jobdate"]   
251        entries = entries[1:]
[2830]252        entries.sort(lambda m, n, si=sortindex : cmp(m[si], n[si]))
[2219]253        for entry in entries :   
[2016]254            printername = entry[fields["printername"]]
255            username = entry[fields["username"]]
256            jobid = entry[fields["jobid"]]
[3050]257            jobdate = DateTime.ISO.ParseDateTime(str(entry[fields["jobdate"]])[:19])
[2016]258            gmtoffset = jobdate.gmtoffset()
259            jobdate = "%s %+03i00" % (jobdate.strftime("%d/%b/%Y:%H:%M:%S"), gmtoffset.hour)
260            jobsize = entry[fields["jobsize"]] or 0
261            copies = entry[fields["copies"]] or 1
262            hostname = entry[fields["hostname"]] or ""
[2217]263            billingcode = entry[fields["billingcode"]] or "-"
[2163]264            for pagenum in range(1, jobsize+1) :
[2217]265                self.outfile.write("%s %s %s [%s] %s %s %s %s\n" % (printername, username, jobid, jobdate, pagenum, copies, billingcode, hostname))
[2600]266        return 0       
[2016]267       
[2600]268    def dumpXml(self, allentries, datatypes) :
[2016]269        """Dumps datas as XML."""
270        x = jaxml.XML_document(encoding="UTF-8")
271        x.pykota(version=version.__version__, author=version.__author__)
[2600]272        for (entries, datatype) in zip(allentries, datatypes) :
[2016]273            x._push()
[2600]274            x.dump(storage=self.config.getStorageBackend()["storagebackend"], type=datatype)
275            headers = entries[0]
276            for entry in entries[1:] :
277                x._push()
278                x.entry()
279                for (header, value) in zip(headers, entry) :
280                    strvalue = str(value)
281                    typval = type(value).__name__
282                    if header in ("filename", "title", "options", "billingcode") \
283                              and (typval == "str") :
284                        try :
285                            strvalue = unicode(strvalue, self.getCharset()).encode("UTF-8")
286                        except UnicodeError :   
287                            pass
288                        strvalue = saxutils.escape(strvalue, { "'" : "&apos;", \
289                                                               '"' : "&quot;" })
290                    x.attribute(strvalue, type=typval, name=header)
291                x._pop()   
[2016]292            x._pop()   
293        x._output(self.outfile)
[2600]294        return 0       
Note: See TracBrowser for help on using the browser.